Compare commits

...
36 Commits
Author SHA1 Message Date
Mohammad Arham 6e942dd2d4 Change: Updated version to v2.2.3 2025-02-05 08:38:31 -05:00
Mohammad Arham 142310a17a Add: SPXW to default for cboe 2025-02-05 12:16:47 +00:00
John Colvin 7f99a89a95 ui (fills): Add notional value column 2025-02-05 00:00:41 -05:00
John Colvin bfce663b4b fix (orderbook): Hook up notional val filter in vm, persist settings 2025-02-04 23:42:58 -05:00
John Colvin 33c743643d fix (orderbook): Include fractional qty in notional val 2025-02-04 23:34:47 -05:00
Taha Afzal 858e0a389b Fixed the issue where a notioanal quoter would send a 0 qty order 2025-02-05 04:16:53 +00:00
Taha Afzal 9c3bb85674 Notional qty handling in spreader 2025-02-05 04:16:53 +00:00
Taha Afzal f4930e8cd4 Added notional to PriceCalc type for the sprader, added code to compute notional books in SpreaderUtils 2025-02-05 04:16:53 +00:00
John Colvin f21e7f3602 ui (orderbook): Show synth notional value in terms of first leg 2025-02-04 16:54:37 -05:00
John Colvin b56c35aca4 ui (ladder): Show synth notional value in terms of first leg 2025-02-04 16:54:37 -05:00
John Colvin e6ac83196e fix (benign): Actually dispose _vm_sub in LadderGridHost 2025-02-04 16:54:37 -05:00
John Colvin 7b6f74fc4a cleanup: Get rid of 'mimic tt' ladder config
This config was leftover from custom DJ work. We no longer need it.
2025-02-04 16:54:37 -05:00
John Colvin f754953fac cleanup: Remove duplicate equality check 2025-02-04 16:54:37 -05:00
Taha 2dd1143d9f Fixed spx spreadbox theos 2025-02-04 16:31:17 -05:00
Mohammad Arham e1ee3275d8 Add: Filter secs upto n months databento 2025-01-31 21:46:11 +00:00
John Colvin 2549e406f3 fix: Compute notional value correctly when ticks per row > 1
It's the sum of price * qty for each level in the row. Previously, we
were multiplying the total qty by the same price (smallest in the row).
2025-01-29 16:28:28 -05:00
John Colvin d5024f1858 fix build: Don't check if unsigned int < 0 2025-01-28 20:20:06 +00:00
John Colvin 3623ac96db fix: Multiply array size by sizeof(T) 2025-01-28 20:19:51 +00:00
John Colvin 19834625f9 fix: Don't fall through switch case 2025-01-28 19:59:32 +00:00
John Colvin c8fc649b6b fix build: Pass config arg to cmake --build
For Ninja Multi-Config builds (i.e., api server and data streams)
2025-01-28 19:59:09 +00:00
John Colvin 6b1fc1c413 cleanup: Remove unused code 2025-01-28 19:59:09 +00:00
John Colvin 6f086532ac cleanup: Convert co2::TradeMessage in DomainSecurityUtils
Instead of domain::TradeRecord constructor. The domain types should have
minimal dependency on protos.
2025-01-28 19:59:09 +00:00
John Colvin dc66282225 cleanup: Rename UINT32 -> VALUE in qty macro names
Should have done this when we changed qty primitive from uint32 to float
2025-01-28 19:59:09 +00:00
John Colvin e2c74c0bb3 cleanup: Don't redefine CONTAINS macro 2025-01-28 19:59:09 +00:00
John Colvin e89d50eec1 cleanup: Move large fns from header to cpp 2025-01-28 19:59:08 +00:00
John Colvin 8bebbc278d cleanup: Get rid of 'using namespaces'
In headers, this is an objectively bad practice, since the type aliases
leak into dependent code and can cause unexpected naming conflicts. In
source files, it is debatable, but I prefer explicit aliases of the form
'using Foo = Bar'. The intentions are clear, and the resulting code is
naturally resistent to naming conflicts.
2025-01-28 19:59:08 +00:00
John Colvin 2e6d12c9c1 cleanup: Remove unnecessary includes 2025-01-28 19:59:08 +00:00
John Colvin 102a5e23ea vscode: Add Tests Sparkd entries to tasks.json 2025-01-28 19:59:08 +00:00
John Colvin 582dc791db vscode: Add Run Sparkd (Skip Build) entry to tasks.json 2025-01-28 19:59:08 +00:00
John Colvin afb35d8c25 Compute synth book qty in terms of first leg 2025-01-28 19:59:08 +00:00
John Colvin 3a32ed5fe6 Fix BookSide equality operators 2025-01-27 16:35:22 +00:00
John Colvin 4e01697327 Add equality and output operators to BookLevel 2025-01-27 16:25:32 +00:00
John Colvin 22032c1742 Add fmt::formatter for domain::Side
So we can pass it to LOG macros, etc.
2025-01-27 16:25:15 +00:00
John Colvin 0a9bce317c Add contract qty info to security proto (and FixedSecurity)
We should get this kind of security metadata from the exchange rather
than config.
2025-01-25 20:41:47 +00:00
Mohammad Arham 40e44db597 Fix: Added GCHandle in attempt to fix ui crash 2025-01-22 02:15:55 -05:00
Mohammad Arham 4a447e44e1 Add: Version to first log 2025-01-22 02:14:19 -05:00
151 changed files with 2524 additions and 1555 deletions

No files matched your search

+62
View File
@@ -699,6 +699,24 @@
"color": "terminal.ansiGreen",
},
},
{
"label": "Run Sparkd (Skip Build)",
"type": "process",
"command": "",
"linux": {
"command": "sparkd",
},
"args": [
"run"
],
"options": {
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
},
"icon": {
"id": "run",
"color": "terminal.ansiGreen",
},
},
{
"label": "Run Sparkd Config Query",
"dependsOn": "Build Sparkd",
@@ -740,6 +758,45 @@
}
},
},
{
"label": "Test Sparkd",
"dependsOn": "Build Sparkd",
"type": "process",
"command": "",
"linux": {
"command": "sparkd",
},
"args": [
"test",
"${input:sparkdTest}",
],
"options": {
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
},
"icon": {
"id": "coverage",
"color": "terminal.ansiYellow",
},
},
{
"label": "Test Sparkd (Skip Build)",
"type": "process",
"command": "",
"linux": {
"command": "sparkd",
},
"args": [
"test",
"${input:sparkdTest}",
],
"options": {
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
},
"icon": {
"id": "coverage",
"color": "terminal.ansiYellow",
},
},
{
"label": "View DataStreamQueryAnalyzer Logs (Last Run)",
"type": "process",
@@ -864,5 +921,10 @@
"type": "promptString",
"description": "Test to run",
},
{
"id": "sparkdTest",
"type": "promptString",
"description": "Test to run",
},
]
}
@@ -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)",
+1 -1
View File
@@ -100,7 +100,7 @@
}
void PerformMEFComposition(string config_source) {
Log.Ui.info($"Generating MEF dependencies for the shell " +
Log.Ui.info($"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;
}
+201 -174
View File
@@ -4,16 +4,11 @@
#include "lib/api/services/OrderService.server.pb.h"
#include "lib/config/config.h"
#include "lib/config/protos/toggles.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/utils/PBUtils.h"
#include <memory>
namespace bts {
namespace admin {
using namespace bts::api::admin;
using namespace bts::api::orderman;
class AdminServiceUtils {
public:
struct ExchangeSubAccountResolver {
@@ -33,7 +28,7 @@ class AdminServiceUtils {
return is_int_account ? entry.bts_account_name()
: entry.exchange_subaccount();
};
if(sec == nullptr)
if (sec == nullptr)
return nullptr;
for (const auto &u : exch_sub_accounts.accounts()) {
if (get_account(u) == account)
@@ -59,224 +54,247 @@ class AdminServiceUtils {
////////////////////////////////////////////
// finds the max id out of all the existing connections
// (todo: deleted connections)
static uint32_t find_max_connection_id(const AdminData &data);
static uint32_t find_max_connection_id(const api::admin::AdminData &data);
// finds a connection by name and returns a mutable pointer to it
static const ExchangeConnection *find_connection(
const AdminData &data, const std::string &connection_name);
static const api::admin::ExchangeConnection *find_connection(
const api::admin::AdminData &data, const std::string &connection_name);
// finds the presence of an exchange in a connection
static bool has_exchange(
const ExchangeConnection &conn, bts::api::Venue exchange);
const api::admin::ExchangeConnection &conn, api::Venue exchange);
// finds the default connections for an exchange
// in a given account
static const ExchangeConnection *find_default_account_connection(
const AdminData &data,
const BTSAccount &account,
bts::api::Venue exchange);
static const ExchangeConnection *resolve_cme_connection(
const AdminData &data,
const BTSAccount &account,
static const api::admin::ExchangeConnection *find_default_account_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
api::Venue exchange);
static const api::admin::ExchangeConnection *resolve_cme_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
static const ExchangeConnection *resolve_ice_connection(
const AdminData &data,
const BTSAccount &account,
static const api::admin::ExchangeConnection *resolve_ice_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
static const ExchangeConnection *resolve_sim_connection(
const AdminData &data,
const BTSAccount &account,
static const api::admin::ExchangeConnection *resolve_sim_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
static const ExchangeConnection *resolve_connection(
const AdminData &data,
const BTSAccount &account,
const bts::api::Security &sec);
static const api::admin::ExchangeConnection *resolve_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
////////////////////////////////////////////
// connection mutators /////////////////////
////////////////////////////////////////////
// adds a new connection (does not check for name uniqueness)
static ExchangeConnection *add_connection(
AdminData &data, const ExchangeConnection &new_connection);
static api::admin::ExchangeConnection *add_connection(
api::admin::AdminData &data,
const api::admin::ExchangeConnection &new_connection);
// reads the connections from config and fills in the
// admin data (with names only)
static void fill_connections_from_config(AdminData &admin_data);
static void fill_connections_from_config(api::admin::AdminData &admin_data);
////////////////////////////////////////////
// account accessors ///////////////////////
////////////////////////////////////////////
// finds the account from the name and returns
// a mutable pointer to the account
static BTSAccount *find_account(
AdminData &data,
static api::admin::BTSAccount *find_account(
api::admin::AdminData &data,
const std::string &acct_name,
bool include_deleted = false);
static std::map<std::string, uint32_t> get_clearing_account_map(
const AdminData &);
const api::admin::AdminData &);
// finds the account from a clearing account (and its venue)
static BTSAccount const *find_account(
const AdminData &data,
static api::admin::BTSAccount const *find_account(
const api::admin::AdminData &data,
const std::string &clearing_account,
bts::api::Venue);
api::Venue);
// same as above for const pointer
static const BTSAccount *find_account(
const AdminData &data,
static const api::admin::BTSAccount *find_account(
const api::admin::AdminData &data,
const std::string &acct_name,
bool include_deleted = false);
// finds the account from the id and returns
// a mutable pointer to the account
static BTSAccount *find_account(
AdminData &data, uint32_t id, bool include_deleted = false);
static api::admin::BTSAccount *find_account(
api::admin::AdminData &data, uint32_t id, bool include_deleted = false);
// like above but returns a const pointer
static const BTSAccount *find_account(
const AdminData &data, uint32_t id, bool include_deleted = false);
static const api::admin::BTSAccount *find_account(
const api::admin::AdminData &data,
uint32_t id,
bool include_deleted = false);
// this function is an indirection layer for account access
// so deletions are dealt in a more deliberate manner
static std::vector<const BTSAccount *> accounts(
const AdminData &data, bool include_deleted);
static std::vector<BTSAccount *> accounts_mutable(
AdminData &data, bool include_deleted);
static std::vector<const api::admin::BTSAccount *> accounts(
const api::admin::AdminData &data, bool include_deleted);
static std::vector<api::admin::BTSAccount *> accounts_mutable(
api::admin::AdminData &data, bool include_deleted);
// finds the max id amongts all the existing accounts
static uint32_t find_max_acct_id(const AdminData &data);
static uint32_t find_max_acct_id(const api::admin::AdminData &data);
// gets the account data for cme (and goes up the hierarchy
// if needed)
static const CMEAccountData *get_account_cme_data(
const BTSAccount &account, const AdminData &data);
static const ICEAccountData *get_account_ice_data(
const BTSAccount &account, const AdminData &data);
static const SIMAccountData *get_account_sim_data(
const BTSAccount &account, const AdminData &data);
static const api::admin::CMEAccountData *get_account_cme_data(
const api::admin::BTSAccount &account,
const api::admin::AdminData &data);
static const api::admin::ICEAccountData *get_account_ice_data(
const api::admin::BTSAccount &account,
const api::admin::AdminData &data);
static const api::admin::SIMAccountData *get_account_sim_data(
const api::admin::BTSAccount &account,
const api::admin::AdminData &data);
// returns true if an account has a connection
bool static account_has_connection(
const BTSAccount &account, const std::string &connection_name);
const api::admin::BTSAccount &account,
const std::string &connection_name);
// finds the external/clearing account associated
// with an account
static std::string find_account_exchange_account(
const AdminData &data,
const BTSAccount &account,
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
api::Venue exchange,
AdminServiceOperationResult &out_result);
api::admin::AdminServiceOperationResult &out_result);
// returns a map of the accounts vs their 'is_active'
static std::map<uint32_t, bool> accounts_map(const AdminData &admin_data);
static std::map<uint32_t, bool> accounts_map(
const api::admin::AdminData &admin_data);
// validates that the update to the account is valid
static void validate_account_update(
const AdminData &admin_data,
const BTSAccount &updated_account,
AdminServiceOperationResult &result);
const api::admin::AdminData &admin_data,
const api::admin::BTSAccount &updated_account,
api::admin::AdminServiceOperationResult &result);
// validates that the account exchange changes are valid
// (ex > 12 characters for accounts at CME)
static void validate_account_exchange_data(
const BTSAccount &account, AdminServiceOperationResult &out_result);
static RoutingRecord get_routing_record(
const AdminData &data,
const api::admin::BTSAccount &account,
api::admin::AdminServiceOperationResult &out_result);
static api::orderman::RoutingRecord get_routing_record(
const api::admin::AdminData &data,
const std::string &account_name,
const std::string &user_name,
const bts::api::Security &sec,
AdminServiceOperationResult &out_result);
static RoutingRecord get_routing_record(
const AdminData &data,
const api::Security &sec,
api::admin::AdminServiceOperationResult &out_result);
static api::orderman::RoutingRecord get_routing_record(
const api::admin::AdminData &data,
uint32_t account_id,
uint32_t user_id,
const bts::api::Security &sec,
AdminServiceOperationResult &out_result);
const api::Security &sec,
api::admin::AdminServiceOperationResult &out_result);
////////////////////////////////////////////
// account mutators ////////////////////////
////////////////////////////////////////////
static BTSAccount *create_account(
AdminData &data,
static api::admin::BTSAccount *create_account(
api::admin::AdminData &data,
uint32_t parent_account_id,
const std::string &account_name,
const std::string &description,
bool is_active,
AdminServiceCreateAccountResult &result);
api::admin::AdminServiceCreateAccountResult &result);
// writes over the account data completely
static void update_account(
AdminData &data,
const BTSAccount &updated_account,
AdminServiceOperationResult &result);
api::admin::AdminData &data,
const api::admin::BTSAccount &updated_account,
api::admin::AdminServiceOperationResult &result);
static void delete_account(
AdminData &admin_data,
const IDAPIArgument &id,
AdminServiceOperationResult &result);
api::admin::AdminData &admin_data,
const api::admin::IDAPIArgument &id,
api::admin::AdminServiceOperationResult &result);
static void set_account_exchange_data(
BTSAccount *account, const ExchangeAccountData &exchange_data);
api::admin::BTSAccount *account,
const api::admin::ExchangeAccountData &exchange_data);
// this is not a generally useful function but since
// connection are in config, this cleans up the account's
// connections and syncs it with the list in AdminData
// connections and syncs it with the list in api::admin::AdminData
static void sync_account_connections(
const AdminData &data, BTSAccount &account);
const api::admin::AdminData &data, api::admin::BTSAccount &account);
////////////////////////////////////////////
// group accessors /////////////////////////
////////////////////////////////////////////
// given an id, finds the mutable pointer to a group
static BTSUserGroup *find_group(AdminData &data, uint32_t id);
static api::admin::BTSUserGroup *find_group(
api::admin::AdminData &data, uint32_t id);
// given an id, finds the const pointer to a group
static const BTSUserGroup *const find_group(
const AdminData &data, uint32_t id);
static const api::admin::BTSUserGroup *const find_group(
const api::admin::AdminData &data, uint32_t id);
// given a name, finds the mutable pointer to a group
static BTSUserGroup *find_group(
AdminData &data, const std::string &group_name);
static api::admin::BTSUserGroup *find_group(
api::admin::AdminData &data, const std::string &group_name);
// finds the max id out of all the groups
static uint32_t find_max_group_id(const AdminData &data);
static uint32_t find_max_group_id(const api::admin::AdminData &data);
// travels up the group tree until it finds a populated cme data
static const CMEUserData *get_group_cme_data(
const BTSUserGroup &group, const AdminData &data);
static const ICEUserData *get_group_ice_data(
const BTSUserGroup &group, const AdminData &data);
static const SIMUserData *get_group_sim_data(
const BTSUserGroup &group, const AdminData &data);
static const UserAccountPermissions *const get_group_user_permissions(
const bts::api::admin::AdminData &admin_data, uint32_t group_id);
static const api::admin::CMEUserData *get_group_cme_data(
const api::admin::BTSUserGroup &group,
const api::admin::AdminData &data);
static const api::admin::ICEUserData *get_group_ice_data(
const api::admin::BTSUserGroup &group,
const api::admin::AdminData &data);
static const api::admin::SIMUserData *get_group_sim_data(
const api::admin::BTSUserGroup &group,
const api::admin::AdminData &data);
static const api::admin::UserAccountPermissions *const
get_group_user_permissions(
const api::admin::AdminData &admin_data, uint32_t group_id);
////////////////////////////////////////////
// group mutators/ /////////////////////////
////////////////////////////////////////////
static BTSUserGroup *create_group(
AdminData &data,
static api::admin::BTSUserGroup *create_group(
api::admin::AdminData &data,
uint32_t parent_group_id,
const std::string &group_name,
AdminServiceOperationResult &result);
api::admin::AdminServiceOperationResult &result);
////////////////////////////////////////////
// user accessors //////////////////////////
////////////////////////////////////////////
static BTSUser *find_user(
AdminData &data,
static api::admin::BTSUser *find_user(
api::admin::AdminData &data,
const std::string &user_name,
bool include_deleted = false);
static const BTSUser *find_user(
const AdminData &data,
static const api::admin::BTSUser *find_user(
const api::admin::AdminData &data,
const std::string &user_name,
bool include_deleted = false);
static BTSUser *find_user(
AdminData &data, uint32_t id, bool include_deleted = false);
static const BTSUser *const find_user(
const AdminData &data, uint32_t id, bool include_deleted = false);
static std::vector<const BTSUser *> users(
const AdminData &data, bool include_deleted);
static std::vector<BTSUser *> users_mutable(
AdminData &data, bool include_deleted);
static uint32_t find_max_user_id(const AdminData &data);
static const CMEUserData *get_user_cme_data(
const BTSUser &user, const AdminData &data);
static const ICEUserData *get_user_ice_data(
const BTSUser &user, const AdminData &data);
static const SIMUserData *get_user_sim_data(
const BTSUser &user, const AdminData &data);
static api::admin::BTSUser *find_user(
api::admin::AdminData &data, uint32_t id, bool include_deleted = false);
static const api::admin::BTSUser *const find_user(
const api::admin::AdminData &data,
uint32_t id,
bool include_deleted = false);
static std::vector<const api::admin::BTSUser *> users(
const api::admin::AdminData &data, bool include_deleted);
static std::vector<api::admin::BTSUser *> users_mutable(
api::admin::AdminData &data, bool include_deleted);
static uint32_t find_max_user_id(const api::admin::AdminData &data);
static const api::admin::CMEUserData *get_user_cme_data(
const api::admin::BTSUser &user, const api::admin::AdminData &data);
static const api::admin::ICEUserData *get_user_ice_data(
const api::admin::BTSUser &user, const api::admin::AdminData &data);
static const api::admin::SIMUserData *get_user_sim_data(
const api::admin::BTSUser &user, const api::admin::AdminData &data);
// finds the "exchange user" (ex: "operator_id" at CME or "authorized_trader"
// at ICE) for a user
static std::string find_user_exchange_id(
const AdminData &data,
const BTSUser &user,
const api::admin::AdminData &data,
const api::admin::BTSUser &user,
api::Venue exchange,
AdminServiceOperationResult &out_result);
api::admin::AdminServiceOperationResult &out_result);
// i. uses the exchange and exchange_user to reverse lookup the user(s)
// ii. uses the exchange, connection and exchange_account to reverse lookup
// the account(s) iii. finds the first account from ii that a user from i is
// authorized to trade
static std::string find_user_company_id(
const AdminData &data,
const BTSUser &user,
const api::admin::AdminData &data,
const api::admin::BTSUser &user,
api::Venue exchange,
AdminServiceOperationResult &out_result);
api::admin::AdminServiceOperationResult &out_result);
static std::pair<const BTSAccount *, const BTSUser *>
static std::pair<const api::admin::BTSAccount *, const api::admin::BTSUser *>
find_user_account_matches(
const AdminData &admin_data,
const api::admin::AdminData &admin_data,
const std::string &connection,
const std::string &exchange_account,
const std::string &exchange_user,
@@ -284,100 +302,108 @@ class AdminServiceUtils {
// for a given user finds the account permissions (if permissions are
// derived from the group, it traverses the group hierarcy to find the
// correct permissions.
static const UserAccountPermissions *get_user_permissions(
const AdminData &admin_data, uint32_t user_id);
static const api::admin::UserAccountPermissions *get_user_permissions(
const api::admin::AdminData &admin_data, uint32_t user_id);
// for a given user, fills in the associated_accounts ref arg
// with the accounts the user has permission to trade in
static void find_user_trading_accounts(
const AdminData &admin_data,
const api::admin::AdminData &admin_data,
uint32_t user_id,
std::vector<const BTSAccount *> &associated_accounts);
std::vector<const api::admin::BTSAccount *> &associated_accounts);
// for a given account, fills in the associated_users ref arg
// with the users that can trade into the account
static void find_users_for_trading_account(
const AdminData &admin_data,
const api::admin::AdminData &admin_data,
uint32_t account_id,
std::vector<const BTSUser *> &associated_users);
std::vector<const api::admin::BTSUser *> &associated_users);
// validates that changes in user data satisfy all the
// constraints
static void validate_user_update(
const AdminData &admin_data,
const BTSUser &updated_user,
AdminServiceOperationResult &result);
const api::admin::AdminData &admin_data,
const api::admin::BTSUser &updated_user,
api::admin::AdminServiceOperationResult &result);
// vaidates the exchange part of user data updates
static void validate_user_exchange_data(
const BTSUser &updated_user, AdminServiceOperationResult &out_result);
const api::admin::BTSUser &updated_user,
api::admin::AdminServiceOperationResult &out_result);
static void get_md_entitlement_report(
const AdminData &admin_data,
std::vector<MDEntitlementReportUnit> &entitlements);
const api::admin::AdminData &admin_data,
std::vector<api::admin::MDEntitlementReportUnit> &entitlements);
// Gets a reporting unit out of CMEMicData
static MDEntitlementReportUnit get_cme_md_entitlement_unit(
const BTSUser &user, const CMEMicData &cme_mic_data);
static MDEntitlementReportUnit get_ice_md_entitlement_unit(
const BTSUser &user, const ICEMicData &ice_mic_data);
static api::admin::MDEntitlementReportUnit get_cme_md_entitlement_unit(
const api::admin::BTSUser &user,
const api::admin::CMEMicData &cme_mic_data);
static api::admin::MDEntitlementReportUnit get_ice_md_entitlement_unit(
const api::admin::BTSUser &user,
const api::admin::ICEMicData &ice_mic_data);
static void get_user_symbol_permissions(
const AdminData &data,
const BTSUser &user,
std::vector<UserSymbolMDPermissionUnit> &res);
const api::admin::AdminData &data,
const api::admin::BTSUser &user,
std::vector<api::admin::UserSymbolMDPermissionUnit> &res);
////////////////////////////////////////////
// user mutators ///////////////////////////
////////////////////////////////////////////
static BTSUser *create_user(
AdminData &data,
static api::admin::BTSUser *create_user(
api::admin::AdminData &data,
uint32_t parent_group_id,
const std::string &name,
const std::string &password,
const std::string &full_name,
const std::string &company,
bool is_active,
const LicenseData &license_data,
const LicenseEntitlements &license_entitlements,
AdminServiceCreateEntityResult &result);
static BTSUser *update_user(
AdminData &admin_data,
const BTSUser &updated_user,
AdminServiceOperationResult &result);
const api::admin::LicenseData &license_data,
const api::admin::LicenseEntitlements &license_entitlements,
api::admin::AdminServiceCreateEntityResult &result);
static api::admin::BTSUser *update_user(
api::admin::AdminData &admin_data,
const api::admin::BTSUser &updated_user,
api::admin::AdminServiceOperationResult &result);
static void delete_user(
AdminData &admin_data,
const IDAPIArgument &id,
AdminServiceOperationResult &result);
api::admin::AdminData &admin_data,
const api::admin::IDAPIArgument &id,
api::admin::AdminServiceOperationResult &result);
static void set_user_exchange_data(
BTSUser *user, const ExchangeUserData &exchange_user_data);
static void clear_user_accounts(BTSUser *user);
api::admin::BTSUser *user,
const api::admin::ExchangeUserData &exchange_user_data);
static void clear_user_accounts(api::admin::BTSUser *user);
static void set_user_accounts(
BTSUser *user, const UserAccountPermissions &user_account_permissions);
api::admin::BTSUser *user,
const api::admin::UserAccountPermissions &user_account_permissions);
////////////////////////////////////////////
// risk limits //////////////////////////////
////////////////////////////////////////////
// sorting function used by sort_limits to
// sort SecurityScope's
static bool _sort_security_scopes(
const SecurityScope &a, const SecurityScope &b);
const api::admin::SecurityScope &a, const api::admin::SecurityScope &b);
// sorting function used by sort_limits to
// sort ProductScope's
static bool _sort_product_scopes(
const ProductScope &a, const ProductScope &);
const api::admin::ProductScope &a, const api::admin::ProductScope &);
// this sorts the rules such that the most
// specific ones are at the top. this makes it
// easier to match rules with securities/products
static void sort_limits(BTSAccount &account);
// creates a copy of AdminData with sorted rules
static AdminData sort_limits(const AdminData &admin_data);
static void sort_limits(api::admin::BTSAccount &account);
// creates a copy of api::admin::AdminData with sorted rules
static api::admin::AdminData sort_limits(
const api::admin::AdminData &admin_data);
// find the clip size for a security, account
static const uint32_t find_account_security_clip_size(
const BTSAccount &account, const api::Security &security);
const api::admin::BTSAccount &account, const api::Security &security);
// find the position limit for an account, security
static const AccountSecurityPositionLimitUnit *
static const api::admin::AccountSecurityPositionLimitUnit *
find_account_security_position_limit(
const BTSAccount &account, const api::Security &security);
const api::admin::BTSAccount &account, const api::Security &security);
// find the position limit for an account, product
static const AccountProductPositionLimitUnit *
static const api::admin::AccountProductPositionLimitUnit *
find_account_product_position_limit(
const BTSAccount &account, const api::Product &product);
const api::admin::BTSAccount &account, const api::Product &product);
static bool match_security_scope(
const SecurityScope &scope, const api::Security &security);
const api::admin::SecurityScope &scope, const api::Security &security);
static bool match_product_scope(
const ProductScope &scope, const api::Product &product);
const api::admin::ProductScope &scope, const api::Product &product);
static bool match_product(
const api::Security &security, const api::Product &product);
static bool _match_limit_rule_symbol(
@@ -388,13 +414,14 @@ class AdminServiceUtils {
////////////////////////////////////////////
static bool has_id(uint32_t id);
static bool is_symbol_set(const std::string &symbol);
static void bootstrap_data(bts::api::admin::AdminData &data);
static void bootstrap_data(api::admin::AdminData &data);
static void get_spread_prod_qty(
const api::Security &s,
std::map<std::string, double> &out_map,
double mult = 1.0);
// returns true if this spread effects the position of any outrights
static bool residual_pos_spread(const api::Security &s);
};
};
} // namespace admin
} // namespace bts
@@ -65,6 +65,7 @@ function(get_cpp_sources dest)
domain/DomainSecurityUtils.cpp
domain/SecurityCriteria.cpp
domain/SecurityCriteriaClause.cpp
domain/synthetic_book_side.cpp
domain_model/Security.cpp
domain_model/SecurityTests.cpp
domain_model/sod.cpp
@@ -64,6 +64,7 @@ function(get_cpp_sources dest)
domain/DomainSecurityUtils.cpp
domain/SecurityCriteria.cpp
domain/SecurityCriteriaClause.cpp
domain/synthetic_book_side.cpp
domain_model/Security.cpp
domain_model/SecurityTests.cpp
domain_model/sod.cpp
@@ -37,6 +37,7 @@ SymbolLists {
list {
key: "CBOE"
symbol:"SPX"
symbol:"SPXW"
}
list {
key: "BINANCE_US"
-2
View File
@@ -39,8 +39,6 @@ LadderKnobs {
security_timeout_secs: 60
use_algo_param_grid: true
max_mbo_orders: 200
lighten_market_cells: true
mimic_tt: true
scrollable_tick_window_size: 256
}
}
+4 -4
View File
@@ -82,7 +82,7 @@ message LadderKnobs {
bool show_scrollbar = 9;
// DJ wants bid/ask cells with markets (i.e., size > 0) to have a
// lighter background than those without markets.
bool lighten_market_cells = 10;
// bool lighten_market_cells = 10;
// For market cell backgrounds, 0 means 'ignore this color setting' (i.e.,
// employ the ladder's default behavior instead).
@@ -97,9 +97,9 @@ message LadderKnobs {
// Pure Red : 0xFFFF0000
// Pure Blue : 0xFF00FF00
// Pure Green : 0xFF0000FF
uint32 bid_market_cell_background = 11;
uint32 ask_market_cell_background = 12;
bool mimic_tt = 13;
// uint32 bid_market_cell_background = 11;
// uint32 ask_market_cell_background = 12;
// bool mimic_tt = 13;
// Defines the number of price rows rendered by the ladder. The smaller this
// window is, the cheaper it is to render the ladder grid (redraw prices,
// sizes, etc.). However, if the window is too small, scrolling may appear
+2 -2
View File
@@ -1,10 +1,10 @@
#pragma once
#include "BookLevel.h"
#include "BookLevelComp.h"
#include "BookSide.h"
#include "SecurityID.h"
#include "Side.h"
#include "lib/io/JsonReader.h"
#include <boost/container/small_vector.hpp>
#include <nlohmann/json.hpp>
+30
View File
@@ -33,6 +33,11 @@ class BookLevel {
void from_json(const nlohmann::json &j, BookLevel &bl);
void to_json(nlohmann::json &j, const BookLevel &bl);
constexpr bool operator==(const BookLevel &lhs, const BookLevel &rhs);
constexpr bool operator!=(const BookLevel &lhs, const BookLevel &rhs);
std::ostream &operator<<(std::ostream &out, const BookLevel &lvl) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
@@ -70,4 +75,29 @@ inline void to_json(nlohmann::json &j, const BookLevel &bl) {
};
}
constexpr bool operator==(const BookLevel &lhs, const BookLevel &rhs) {
return lhs.direct_quantity == rhs.direct_quantity &&
lhs.total_quantity == rhs.total_quantity &&
lhs.n_orders == rhs.n_orders &&
(lhs.price == rhs.price ||
(lhs.price.is_nan() && rhs.price.is_nan()));
}
constexpr bool operator!=(const BookLevel &lhs, const BookLevel &rhs) {
return !(lhs == rhs);
}
inline std::ostream &operator<<(
std::ostream &out, const BookLevel &lvl) noexcept {
out << lvl.price << " (" << lvl.direct_quantity;
if (lvl.direct_quantity != lvl.total_quantity) {
out << "/" << lvl.total_quantity;
}
if (lvl.n_orders != 0) {
out << ", " << lvl.n_orders;
}
out << ")";
return out;
}
} // namespace bts::domain
+31 -7
View File
@@ -1,18 +1,17 @@
#pragma once
#include "BookLevel.h"
#include "BookLevelComp.h"
#include "BookSide_PopulatedIterator.h"
#include "PriceComp.h"
#include "SecurityID.h"
#include "Side.h"
#include "lib/io/JsonReader.h"
#include <algorithm>
#include <boost/container/small_vector.hpp>
#include <initializer_list>
#include <iostream>
#include <iterator>
#include <limits>
#include <random>
#include <vector>
namespace bts::domain {
@@ -37,6 +36,8 @@ class BookSideT {
BookSideT &operator=(BookSideT &&) noexcept = default;
~BookSideT() = default;
constexpr BookLevel *data();
constexpr const BookLevel *data() const;
[[nodiscard]] constexpr BookLevel const &top() const;
constexpr BookLevel const &operator[](size_t i) const;
constexpr BookLevel &operator[](size_t i);
@@ -196,6 +197,16 @@ constexpr size_t BookSideT<SZ>::max_depth() noexcept {
return SZ;
}
template <std::size_t SZ>
constexpr BookLevel *BookSideT<SZ>::data() {
return _book.data();
}
template <std::size_t SZ>
constexpr const BookLevel *BookSideT<SZ>::data() const {
return _book.data();
}
template <std::size_t SZ>
constexpr const BookLevel &BookSideT<SZ>::top() const {
return _book[0];
@@ -234,12 +245,14 @@ BookSideT<SZ>::end() const noexcept {
}
template <std::size_t SZ>
constexpr typename BookSideT<SZ>::ReverseIterator BookSideT<SZ>::rbegin() noexcept {
constexpr typename BookSideT<SZ>::ReverseIterator
BookSideT<SZ>::rbegin() noexcept {
return _book.rbegin();
}
template <std::size_t SZ>
constexpr typename BookSideT<SZ>::ReverseIterator BookSideT<SZ>::rend() noexcept {
constexpr typename BookSideT<SZ>::ReverseIterator
BookSideT<SZ>::rend() noexcept {
return _book.rend();
}
@@ -250,12 +263,23 @@ constexpr size_t BookSideT<SZ>::size() const noexcept {
template <std::size_t SZ>
constexpr bool BookSideT<SZ>::operator==(BookSideT const &rhs) const {
return _book == rhs._book;
std::size_t i = 0;
for (; i < size(); i++) {
const BookLevel &l = (*this)[i];
if (i == rhs.size()) {
return l.is_empty();
}
const BookLevel &r = rhs[i];
if (l != r) {
return l.is_empty() && r.is_empty();
}
}
return i == rhs.size() || rhs[i].is_empty();
}
template <std::size_t SZ>
constexpr bool BookSideT<SZ>::operator!=(BookSideT const &rhs) const {
return _book != rhs._book;
return !(*this == rhs);
}
template <std::size_t SZ>
+77 -7
View File
@@ -1,16 +1,14 @@
#include "DomainSecurityUtils.h"
#include "FutureSecurity.h"
#include "OptionSecurity.h"
#include "SecurityCommon.h"
#include "SpotSecurity.h"
#include "SpreadSecurity.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/domain/TradeRecord.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/md/IntPrice.h"
#include "lib/md/PriceSize.h"
#include "lib/md/protos/co2.pb.h"
#include "lib/utils/bts_assert.h"
// #include "lib/domain/SecurityID.h"
// #include "lib/utils/overloaded.h"
// #include <fmt/format.h>
// #include <variant>
namespace bts::domain {
@@ -62,7 +60,7 @@ bts::api::Venue DomainSecurityUtils::convert(api::domain::Venue venue) {
case DOMAIN_VENUE(CBOE):
return api::Venue::VENUE_CBOE;
case DOMAIN_VENUE(TAIFEX):
return api::Venue::VENUE_TAIFEX;
return api::Venue::VENUE_TAIFEX;
case DOMAIN_VENUE(FTX):
return api::Venue::VENUE_FTX;
case DOMAIN_VENUE(BINANCE):
@@ -219,4 +217,76 @@ bool DomainSecurityUtils::get_security_desc(
return true;
}
domain::Price DomainSecurityUtils::convert(md::IntPrice iprice) {
int64_t mantissa = iprice.mantissa();
int8_t exp_diff = iprice.exponent() - domain::Price::exponent();
if (exp_diff != 0) {
mantissa *= std::pow(10, exp_diff);
}
return domain::Price{mantissa};
}
BookLevel DomainSecurityUtils::convert(md::PriceSize ps) {
BookLevel rv;
convert(ps, rv);
return rv;
}
void DomainSecurityUtils::convert(md::PriceSize ps, BookLevel &res) {
res.price = convert(ps.int_price());
res.total_quantity.value() = ps.size();
res.direct_quantity.value() = ps.direct_size();
res.n_orders = 0;
}
void DomainSecurityUtils::convert(
const bts::domain::BookSide &book_side,
bts::md::PriceSize *res,
size_t res_size) {
convert(book_side.data(), book_side.size(), res, res_size);
}
void DomainSecurityUtils::convert(
const BookLevel *book_side,
size_t size,
md::PriceSize *res,
size_t res_size) {
size_t sz = std::min(size, res_size);
size_t i = 0;
for (; i < sz; i++) {
// WARNING: Implied size is discarded here. There would be data loss anyways, since
// bts::md::PriceSize's implied size is a uint, and domain::BookLevel's is a float.
res[i] = md::PriceSize{
md::IntPrice{
book_side[i].price.mantissa(), book_side[i].price.exponent()},
book_side[i].total_quantity.value()};
}
// Set remaining levels to empty.
for (; i < res_size; i++) {
res[i] = bts::md::PriceSize{};
}
}
void DomainSecurityUtils::convert(
const md::PriceSize *book_side, std::size_t len, BookSide &res) {
if (res.size() < len) {
len = res.size();
}
while (len-- != 0) {
convert(book_side[len], res[len]);
}
}
void DomainSecurityUtils::convert(
const co2::TradeMessage &tm, TradeRecord &tr) {
tr.sec_id.value() = tm.key();
tr.time_stamp = core::TimeStamp::from_nanos(tm.time());
tr.agg_side = tm.aggressor_side() == co2::AggressorSide::AGGRESSOR_SIDE_BUY
? AggressorSide::Buy
: AggressorSide::Sell;
tr.trade_quantity.value() = tm.size();
tr.trade_price =
convert(md::IntPrice{tm.mantissa(), static_cast<int8_t>(tm.exponent())});
}
} // namespace bts::domain
+29 -4
View File
@@ -1,18 +1,28 @@
#pragma once
#include "FutureSecurity.h"
#include "OptionSecurity.h"
#include "SpotSecurity.h"
#include "SpreadSecurity.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/domain/SecurityID.h"
#include "lib/domain/BookSide.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/utils/overloaded.h"
#include <fmt/format.h>
#include <variant>
namespace bts::co2 {
class TradeMessage;
}
namespace bts::md {
class IntPrice;
class PriceSize;
} // namespace bts::md
namespace bts::domain {
class TradeRecord;
class DomainSecurityUtils {
public:
static api::domain::Venue convert(bts::api::Venue venue);
@@ -29,6 +39,21 @@ class DomainSecurityUtils {
static std::string get_exchange_id(const bts::api::Security &s);
static bool get_security_desc(
const bts::api::Security &security, std::string &desc);
static Price convert(md::IntPrice iprice);
static BookLevel convert(md::PriceSize ps);
static void convert(md::PriceSize ps, BookLevel &res);
static void convert(
const BookSide &book_side, md::PriceSize *res, std::size_t res_size);
static void convert(
const BookLevel *book_side,
std::size_t size,
md::PriceSize *res,
std::size_t res_size);
static void convert(
const md::PriceSize *book_side, std::size_t len, BookSide &res);
static void convert(const co2::TradeMessage &tm, TradeRecord &tr);
};
} // namespace bts::domain
+2 -2
View File
@@ -1,13 +1,13 @@
#pragma once
#include "AccountID.h"
#include "Price.h"
#include "Quantity.h"
#include "SecurityID.h"
#include "Side.h"
#include "TimeInForce.h"
#include "UserID.h"
#include "lib/domain/cme/GlbxOrderDefinition.h"
#include "lib/domain/deribit/DeribitOrderDefinition.h"
#include <string>
#include <variant>
+20 -21
View File
@@ -5,7 +5,6 @@
#include "lib/logging/logging.h"
#include "lib/utils/ValueWrapper.h"
#include <cstdint>
#include <nlohmann/json.hpp>
namespace bts::domain {
@@ -77,7 +76,7 @@ inline void to_json(nlohmann::json &j, const Quantity &sec_id) {
return lhs; \
}
#define QTY_UINT32_BIN_OP(result_t, op) \
#define QTY_VALUE_BIN_OP(result_t, op) \
constexpr result_t operator op(Quantity lhs, Quantity::Value rhs) { \
return result_t(lhs.value() op rhs); /*NOLINT*/ \
} \
@@ -85,7 +84,7 @@ inline void to_json(nlohmann::json &j, const Quantity &sec_id) {
return result_t(lhs op rhs.value()); \
}
#define QTY_UINT32_BIN_REF_OP(op) \
#define QTY_VALUE_BIN_REF_OP(op) \
inline Quantity &operator op(Quantity &lhs, Quantity::Value rhs) { \
lhs.value() op rhs; \
return lhs; \
@@ -107,29 +106,29 @@ QTY_QTY_BIN_REF_OP(*=)
QTY_QTY_BIN_REF_OP(/=)
// QTY_QTY_BIN_REF_OP(%=)
QTY_UINT32_BIN_OP(bool, !=)
QTY_UINT32_BIN_OP(bool, ==)
QTY_UINT32_BIN_OP(bool, <)
QTY_UINT32_BIN_OP(bool, <=)
QTY_UINT32_BIN_OP(bool, >)
QTY_UINT32_BIN_OP(bool, >=)
QTY_VALUE_BIN_OP(bool, !=)
QTY_VALUE_BIN_OP(bool, ==)
QTY_VALUE_BIN_OP(bool, <)
QTY_VALUE_BIN_OP(bool, <=)
QTY_VALUE_BIN_OP(bool, >)
QTY_VALUE_BIN_OP(bool, >=)
QTY_UINT32_BIN_OP(Quantity, +)
QTY_UINT32_BIN_OP(Quantity, -)
QTY_UINT32_BIN_OP(Quantity, *)
QTY_UINT32_BIN_OP(Quantity, /)
// QTY_UINT32_BIN_OP(Quantity, %)
QTY_VALUE_BIN_OP(Quantity, +)
QTY_VALUE_BIN_OP(Quantity, -)
QTY_VALUE_BIN_OP(Quantity, *)
QTY_VALUE_BIN_OP(Quantity, /)
// QTY_VALUE_BIN_OP(Quantity, %)
QTY_UINT32_BIN_REF_OP(+=)
QTY_UINT32_BIN_REF_OP(-=)
QTY_UINT32_BIN_REF_OP(*=)
QTY_UINT32_BIN_REF_OP(/=)
// QTY_UINT32_BIN_REF_OP(%=)
QTY_VALUE_BIN_REF_OP(+=)
QTY_VALUE_BIN_REF_OP(-=)
QTY_VALUE_BIN_REF_OP(*=)
QTY_VALUE_BIN_REF_OP(/=)
// QTY_VALUE_BIN_REF_OP(%=)
#undef QTY_QTY_BIN_OP
#undef QTY_QTY_BIN_REF_OP
#undef QTY_UINT32_BIN_OP
#undef QTY_UINT32_BIN_REF_OP
#undef QTY_VALUE_BIN_OP
#undef QTY_VALUE_BIN_REF_OP
} // namespace bts::domain
-1
View File
@@ -1,6 +1,5 @@
#include "Security.h"
#include "DomainSecurityUtils.h"
#include "lib/core/TableFormatter.h"
#include "lib/domain/FutureSecurity.h"
#include "lib/domain/OptionSecurity.h"
#include "lib/domain/SpotSecurity.h"
+3 -13
View File
@@ -1,22 +1,12 @@
#pragma once
#include "DomainSecurityUtils.h"
#include "Price.h"
#include "SecurityID.h"
#include "lib/api/domain/CallPut.pb.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/core/TimeStamp.h"
#include "lib/domain/FutureSecurity.h"
#include "lib/domain/LegDefinition.h"
#include "lib/domain/OptionSecurity.h"
#include "lib/domain/Quantity.h"
#include "lib/domain/Side.h"
#include "lib/domain/SpreadSecurity.h"
#include "lib/domain/DomainSecurityUtils.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/bts_assert.h"
#include <limits>
#include <nlohmann/json.hpp>
#include <string>
@@ -40,7 +30,7 @@ class SecurityCommon {
double min_quantity_increment = 0;
double min_quantity = 0;
double max_quantity = std::numeric_limits<double>::max();
double cash_per_point =1;
double cash_per_point = 1;
SecurityCommon() {}
SecurityCommon(const bts::api::Security &s)
@@ -21,9 +21,6 @@ using std::regex_search;
using std::smatch;
using std::string;
#define CONTAINS(container, value) \
(std::find(container.begin(), container.end(), value) != container.end())
bool SecurityCriteriaClause::is_defined() const {
return !venues.empty() || !types.empty() || !assets.empty() ||
!groups.empty() || !ids.empty() || !names.empty() ||
@@ -1,11 +1,13 @@
#pragma once
#include "Price.h"
#include "Quantity.h"
#include "Security.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain/SecurityID.h"
#include "lib/io/JsonReader.h"
#include "lib/md/IntPrice.h"
#include "lib/md/protos/co2.pb.h"
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <ostream>
+5
View File
@@ -1,4 +1,7 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include <ostream>
namespace bts::domain {
@@ -153,3 +156,5 @@ constexpr Side operator*(Side lhs, Side::Value rhs) noexcept {
}
} // namespace bts::domain
FMT_IMPL(bts::domain::Side, s, "{}", s.name());
+2 -14
View File
@@ -1,12 +1,12 @@
#pragma once
#include "AggressorSide.h"
#include "Price.h"
#include "Quantity.h"
#include "SecurityID.h"
#include "lib/core/TimeStamp.h"
#include "lib/logging/logging.h"
#include "lib/md/IntPrice.h"
#include "lib/md/protos/co2.pb.h"
#include <nlohmann/json.hpp>
namespace bts::domain {
@@ -18,8 +18,6 @@ class TradeRecord {
AggressorSide agg_side = {};
Quantity trade_quantity;
Price trade_price;
TradeRecord() {}
inline TradeRecord(const bts::co2::TradeMessage &tm);
};
void from_json(const nlohmann::json &j, TradeRecord &tr);
@@ -29,16 +27,6 @@ void to_json(nlohmann::json &j, const TradeRecord &tr);
// Definition
//////////////////////////////////////////////////
inline TradeRecord::TradeRecord(const bts::co2::TradeMessage &tm)
: sec_id(tm.key()), time_stamp(tm.time()),
agg_side(
tm.aggressor_side() == co2::AggressorSide::AGGRESSOR_SIDE_BUY
? AggressorSide{AggressorSide::Buy}
: AggressorSide{AggressorSide::Sell}),
trade_quantity(tm.size()),
trade_price(Price::from_double(
bts::md::IntPrice(tm.mantissa(), tm.exponent()).price())) {}
inline void from_json(const nlohmann::json &j, TradeRecord &tr) {
io::JsonReader jr{&j};
// clang-format off
+107
View File
@@ -0,0 +1,107 @@
#pragma once
#include "lib/domain/BookSide.h"
#include "lib/domain/Quantity.h"
namespace bts::domain {
class BookSideConsumer {
// Use pointer so copying is cheap
const BookLevel *levels_;
std::size_t size_;
std::size_t curr_{0};
Quantity curr_consumed_{0};
public:
BookSideConsumer() = default;
BookSideConsumer(const BookLevel *levels, std::size_t size)
: levels_(levels), size_(size) {}
template <std::size_t SZ>
BookSideConsumer(
const BookSideT<SZ> *levels,
std::size_t curr = 0,
Quantity curr_consumed = Quantity{0});
BookSideConsumer(const BookSideConsumer &other) = default;
BookSideConsumer &operator=(BookSideConsumer &&other) = default;
BookSideConsumer &operator=(const BookSideConsumer &other) = default;
const BookLevel &operator[](std::size_t idx) const;
const BookLevel *levels() const { return levels_; }
size_t size() const;
bool is_done() const;
void reset();
Quantity available(std::size_t level_limit = 1) const;
std::pair<Quantity, Price> consume(Quantity quantity);
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
template <std::size_t SZ>
inline BookSideConsumer::BookSideConsumer(
const BookSideT<SZ> *levels, std::size_t curr, Quantity curr_consumed)
: levels_(levels), curr_(curr), curr_consumed_(curr_consumed) {}
inline const BookLevel &BookSideConsumer::operator[](std::size_t idx) const {
return levels_[idx];
}
inline size_t BookSideConsumer::size() const { return size_; }
inline bool BookSideConsumer::is_done() const {
if (curr_ >= size_) {
return true;
}
if (curr_ == size_ - 1) {
return curr_consumed_ >= levels_[curr_].total_quantity;
}
return curr_consumed_ >= levels_[curr_].total_quantity &&
levels_[curr_ + 1].total_quantity == 0;
}
inline void BookSideConsumer::reset() {
curr_ = 0;
curr_consumed_.value() = 0;
}
inline Quantity BookSideConsumer::available(std::size_t level_limit) const {
std::size_t end = size_ > curr_ + level_limit ? curr_ + level_limit : size_;
Quantity rv{0};
for (std::size_t i = curr_; i < end; i++) {
Quantity level_qty = levels_[i].total_quantity;
if (i == curr_) {
level_qty -= curr_consumed_;
}
rv += level_qty;
}
return rv;
}
inline std::pair<Quantity, Price> BookSideConsumer::consume(Quantity quantity) {
Quantity consumed{0};
Price price = Price::zero();
for (; curr_ < size_; curr_++) {
Quantity level_qty = levels_[curr_].total_quantity - curr_consumed_;
if (level_qty == 0) {
break;
}
Quantity rem = quantity - consumed;
if (level_qty > rem) {
curr_consumed_ += rem;
consumed = quantity;
price += levels_[curr_].price * rem.value();
break;
}
curr_consumed_.value() = 0;
consumed += level_qty;
price += levels_[curr_].price * level_qty.value();
}
return {consumed, price};
}
} // namespace bts::domain
+116
View File
@@ -0,0 +1,116 @@
#include "synthetic_book_side.h"
#include "lib/domain/book_side_consumer.h"
namespace bts::domain {
void SyntheticBookSide::copy_(const SyntheticBookSide &other) noexcept {
if (this == &other) {
return;
}
leg_qty_.value() = other.leg_qty_.value();
// Copy 'data_'
if (other.data_ != nullptr) {
// Clear 'own_data_'
if (own_data_ != nullptr) {
delete[] own_data_;
own_data_ = nullptr;
}
size_ = other.size_;
data_ = other.data_;
return;
}
data_ = nullptr;
if (other.size_ == 0) {
// Clear 'own_data_'
if (own_data_ != nullptr) {
delete[] own_data_;
own_data_ = nullptr;
}
size_ = 0;
return;
}
// Copy 'own_data_'
if (own_data_ == nullptr) {
own_data_ = new BookLevel[other.size_];
} else if (size_ != other.size_) {
delete[] own_data_;
own_data_ = new BookLevel[other.size_];
}
size_ = other.size_;
std::memcpy(own_data_, other.own_data_, size_ * sizeof(BookLevel));
}
void SyntheticBookSide::move_(SyntheticBookSide &&other) noexcept {
if (own_data_ != nullptr) {
delete[] own_data_;
}
leg_qty_.value() = other.leg_qty_.value();
data_ = other.data_;
size_ = other.size_;
own_data_ = other.own_data_;
other.leg_qty_.value() = 0;
other.data_ = nullptr;
other.own_data_ = nullptr;
other.size_ = 0;
}
SyntheticBookSide SyntheticBookSide::add_leg(
SyntheticBookSide other_leg,
BookLevel *synth_bk,
size_t synth_bk_size) const noexcept {
if (leg_qty_.value() == 0) {
return other_leg;
}
if (other_leg.leg_qty_.value() == 0) {
return *this;
}
SyntheticBookSide rv;
rv.leg_qty_.value() = 1;
rv.size_ = synth_bk_size;
if (synth_bk == nullptr) {
rv.own_data_ = synth_bk = new BookLevel[synth_bk_size];
} else {
rv.data_ = synth_bk;
}
BookSideConsumer l1{data(), size_};
BookSideConsumer l2{other_leg.data(), other_leg.size_};
Quantity l1_qty = leg_qty_;
Quantity l2_qty = other_leg.leg_qty_;
std::size_t i = 0;
for (; !l1.is_done() && !l2.is_done() && i < synth_bk_size; i++) {
Quantity leg2_per_leg1 = (l2_qty / l1_qty);
if (leg2_per_leg1 < 0) {
leg2_per_leg1.value() = -leg2_per_leg1.value();
}
Quantity leg1_per_leg2 = (l1_qty / l2_qty);
if (leg1_per_leg2 < 0) {
leg1_per_leg2.value() = -leg1_per_leg2.value();
}
Quantity avail1 = l1.available();
Quantity avail2 = l2.available();
Quantity take1;
Quantity take2;
if ((avail1 / leg1_per_leg2) < (avail2 / leg2_per_leg1)) {
take1 = avail1;
take2 = avail1 * leg2_per_leg1;
} else {
take1 = avail2 * leg1_per_leg2;
take2 = avail2;
}
const auto [qty1, cost1] = l1.consume(take1);
const auto [qty2, cost2] = l2.consume(take2);
Price price1 = (cost1 / qty1.value()) * l1_qty.value();
Price price2 = (cost2 / qty2.value()) * l2_qty.value();
auto &synth_lvl = synth_bk[i];
synth_lvl.price = price1 + price2;
synth_lvl.total_quantity = qty1;
synth_lvl.direct_quantity = qty1;
synth_lvl.n_orders = 0;
}
for (; i < synth_bk_size; i++) {
synth_bk[i] = BookLevel::empty();
}
return rv;
}
} // namespace bts::domain
+112
View File
@@ -0,0 +1,112 @@
#pragma once
#include "lib/domain/BookLevel.h"
#include "lib/domain/Quantity.h"
#include <cstddef>
#include <cstring>
namespace bts::domain {
class SyntheticBookSide {
Quantity leg_qty_{0};
// Either 'data_' or 'own_data_' can be non-null (not both). Only the latter
// is deleted in the destructor.
const BookLevel *data_{nullptr};
BookLevel *own_data_{nullptr};
std::size_t size_{0};
void copy_(const SyntheticBookSide &other) noexcept;
void move_(SyntheticBookSide &&other) noexcept;
public:
constexpr SyntheticBookSide() = default;
constexpr SyntheticBookSide(
Quantity leg_qty, const BookLevel *book_side, std::size_t size);
constexpr ~SyntheticBookSide();
SyntheticBookSide(const SyntheticBookSide &other);
SyntheticBookSide(SyntheticBookSide &&other);
SyntheticBookSide &operator=(const SyntheticBookSide &other);
SyntheticBookSide &operator=(SyntheticBookSide &&other);
constexpr std::size_t size() const;
constexpr const BookLevel *data() const;
/**
* Adds a leg to this one (calls can be chained to get the depth for N legs).
*
* @return SyntheticBookSide whose levels constitute the synthetic depth of
* this and the specified @p leg. Its quantity is always 1.
*/
SyntheticBookSide add_leg(SyntheticBookSide leg) const noexcept;
SyntheticBookSide add_leg(
SyntheticBookSide other_leg,
BookLevel *synth_bk,
size_t synth_bk_size) const noexcept;
/**
* Syntactic sugar for `add_leg`
*/
SyntheticBookSide operator+(SyntheticBookSide other) const noexcept;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
constexpr SyntheticBookSide::SyntheticBookSide(
Quantity leg_qty, const BookLevel *data, std::size_t size)
: leg_qty_(leg_qty), data_(data), own_data_(nullptr), size_(size) {}
constexpr SyntheticBookSide::~SyntheticBookSide() {
leg_qty_.value() = 0;
data_ = nullptr;
if (own_data_ != nullptr) {
delete[] own_data_;
own_data_ = nullptr;
}
size_ = 0;
}
inline SyntheticBookSide::SyntheticBookSide(const SyntheticBookSide &other) {
copy_(other);
}
inline SyntheticBookSide::SyntheticBookSide(SyntheticBookSide &&other) {
move_(std::move(other));
}
inline SyntheticBookSide &SyntheticBookSide::operator=(
const SyntheticBookSide &other) {
if (this != &other) {
copy_(other);
}
return *this;
}
inline SyntheticBookSide &SyntheticBookSide::operator=(
SyntheticBookSide &&other) {
if (this != &other) {
move_(std::move(other));
}
return *this;
}
constexpr std::size_t SyntheticBookSide::size() const { return size_; }
constexpr const BookLevel *SyntheticBookSide::data() const {
return data_ == nullptr ? own_data_ : data_;
}
inline SyntheticBookSide SyntheticBookSide::add_leg(
SyntheticBookSide other_leg) const noexcept {
return add_leg(other_leg, nullptr, 10);
}
inline SyntheticBookSide SyntheticBookSide ::operator+(
SyntheticBookSide other) const noexcept {
return add_leg(other);
}
} // namespace bts::domain
+1 -4
View File
@@ -1,10 +1,8 @@
#include "Security.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/config/config.h"
#include "lib/config/protos/orders_fills_cleanup.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/md/common.h"
#include "lib/utils/datetime.h"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <google/protobuf/text_format.h>
@@ -13,7 +11,6 @@ namespace bts {
namespace securities {
using bts::api::Security;
using bts::api::Venue;
using std::optional;
api::SecurityType security_type(api::Security const &security) {
-2
View File
@@ -7,10 +7,8 @@
#include "lib/domain_model/Venue.h"
#include "lib/domain_model/common.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/datetime.h"
#include <cmath>
#include <memory>
namespace boost {
namespace gregorian {
+12
View File
@@ -205,10 +205,20 @@ message SyntheticContract {
enum PriceCalc {
PRICECALC_STANDARD = 0;
PRICECALC_NET_CHANGE = 1;
PRICECALC_NOTIONAL_LEG = 2;
// RATIO = 2;
}
}
message ContractQuantityInfo {
double min_value = 1;
double max_value = 2;
double tick_unit = 3;
double tick_base = 4;
double multiplier = 5;
double max_price = 6;
}
message ContractSpecification {
double low_tick = 10;
double price_break = 20;
@@ -219,6 +229,8 @@ message ContractSpecification {
uint32 main_fraction = 60;
uint32 sub_fraction = 61;
uint32 price_display_format = 62;
repeated ContractQuantityInfo quantity_infos = 63;
repeated ContractQuantityInfo notional_quantity_infos = 64;
}
message Security {
+239 -132
View File
@@ -1,6 +1,4 @@
//#ifdef SPARK_ENABLE_CCAPI
#include "GeneralMarketDataListener.h"
#include "lib/api/domain/Currency.pb.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/core/TimeStamp.h"
@@ -21,7 +19,6 @@
#include "lib/domain/SpreadSecurity.h"
#include "lib/domain/TradeRecord.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/common.h"
#include "lib/logging/logging.h"
#include "lib/md/GeneralSecurityIndex.h"
#include "lib/md/IMarketDataListener.h"
@@ -29,28 +26,19 @@
#include "lib/md/MDProducer.h"
#include "lib/md/MDProducerHandle.h"
#include "lib/md/PriceSize.h"
#include "lib/md/SecurityIndex.h"
#include "lib/md/Shm/FixedSecurity.h"
#include "lib/md/Shm/MDStructure.h"
#include "lib/md/protos/co2.pb.h"
#include "lib/orderman/actors/SODPosition.h"
#include "lib/orderman/gateways/sim/SimExecutionGateway.h"
#include "lib/orderman/gateways/sim/SimExecutionGatewayFactory.h"
#include "lib/orderman/orderman_utils/Book.h"
#include "lib/os/os.h"
#include "lib/persistence/IDGenerator.h"
#include "lib/utils/PBUtils.h"
#include "lib/utils/TimeStamp.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/bts_fatal.h"
#include <cstddef>
#include <cstdio>
#include <memory>
#include <bits/stdint-uintn.h>
#include <cstdio>
#include <google/protobuf/timestamp.pb.h>
#include <google/protobuf/util/time_util.h>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
@@ -80,74 +68,88 @@ struct CCSecSearchResult {
struct IncrementalBookData {
std::map<domain::SecurityID, domain::Book> book_map;
bool update_incremental(const IMarketDataListener::BookOp &op) {
//DONT NEED THIS?
// Validate inputs first
if (op.price <= 0.0 || op.quantity < 0) {
LOG(md, Error, "Invalid input - Price: {}, Quantity: {}", op.price, op.quantity);
return false;
}
bool update_incremental(const IMarketDataListener::BookOp &op) {
//DONT NEED THIS?
// Validate inputs first
if (op.price <= 0.0 || op.quantity < 0) {
LOG(md, Error, "Invalid input - Price: {}, Quantity: {}", op.price,
op.quantity);
return false;
}
// Logging setup
//DO WE NEED TO PUT THESE IN A STRING
std::string side_str = (op.side == domain::Side::Buy) ? "Buy" : "Sell";
std::string op_type_str;
switch (op.op_type) {
case IMarketDataListener::BookOpType::Add: op_type_str = "Add"; break;
case IMarketDataListener::BookOpType::MODIFY: op_type_str = "Modify"; break;
case IMarketDataListener::BookOpType::REMOVE: op_type_str = "Delete"; break;
default: op_type_str = "Unknown"; break;
}
//CAN WE DO THIS WITHOUT ASSIGNING TO STRINGS
std::stringstream op_details;
op_details << "Operation: Price: " << op.price << ", Quantity: " << op.quantity
<< ", Side: " << side_str << ", Type: " << op_type_str << "\n";
// Logging setup
//DO WE NEED TO PUT THESE IN A STRING
std::string side_str = (op.side == domain::Side::Buy) ? "Buy" : "Sell";
std::string op_type_str;
switch (op.op_type) {
case IMarketDataListener::BookOpType::Add:
op_type_str = "Add";
break;
case IMarketDataListener::BookOpType::MODIFY:
op_type_str = "Modify";
break;
case IMarketDataListener::BookOpType::REMOVE:
op_type_str = "Delete";
break;
default:
op_type_str = "Unknown";
break;
}
//CAN WE DO THIS WITHOUT ASSIGNING TO STRINGS
std::stringstream op_details;
op_details << "Operation: Price: " << op.price
<< ", Quantity: " << op.quantity << ", Side: " << side_str
<< ", Type: " << op_type_str << "\n";
LOG(md, Info, "Book Increment: {}", op_details.str());
LOG(md, Info, "Book Increment: {}", op_details.str());
// Get book and validate
auto book = get_book(op.id);
if (book == nullptr) {
LOG(md, Error, "Book not found for id: {}", op.id);
return false;
}
// Get book and validate
auto book = get_book(op.id);
if (book == nullptr) {
LOG(md, Error, "Book not found for id: {}", op.id);
return false;
}
// Get correct side of the book
auto& book_side = (op.side == domain::Side::Buy) ? book->bids() : book->asks();
// Get correct side of the book
auto &book_side =
(op.side == domain::Side::Buy) ? book->bids() : book->asks();
// Log initial state
std::stringstream book_before;
book_before << "Book before update (" << side_str << " side):\n";
for (const auto& level : book_side) {
book_before << "Price: " << level.price << ", Quantity: " << level.total_quantity << "\n";
}
// Log initial state
std::stringstream book_before;
book_before << "Book before update (" << side_str << " side):\n";
for (const auto &level : book_side) {
book_before << "Price: " << level.price
<< ", Quantity: " << level.total_quantity << "\n";
}
/*-----------*/
if(op.side == domain::Side::Buy){
switch(op.op_type){
case IMarketDataListener::BookOpType::Add:
{
/*-----------*/
if (op.side == domain::Side::Buy) {
switch (op.op_type) {
case IMarketDataListener::BookOpType::Add: {
int insert_position = 0;
for (size_t i = 0; i < book_side.size(); i++){//not sure how to access book
if(book_side[i].price != 0 && book_side[i].price > op.price ){
for (size_t i = 0; i < book_side.size();
i++) { //not sure how to access book
if (book_side[i].price != 0 && book_side[i].price > op.price) {
//book_side is the book we have ?
//op.price is what we received ?
insert_position++;
continue;
}else {//need j to be last level
for (size_t j =fmin(book_side.size()-1,5-1); j > i; j--){
book_side[j] = book_side[j-1];
} else { //need j to be last level
for (size_t j = fmin(book_side.size() - 1, 5 - 1); j > i;
j--) {
book_side[j] = book_side[j - 1];
}
break;
}
}
book_side[insert_position].price = op.price;
book_side[insert_position].total_quantity = op.quantity;//dont know how to add quantity
book_side[insert_position].total_quantity =
op.quantity; //dont know how to add quantity
break;
}
case IMarketDataListener::BookOpType::MODIFY:{
for(size_t i = 0; i < fmin(book_side.size(),5); i++){
if(book_side[i].price != 0 && book_side[i].price == op.price){
case IMarketDataListener::BookOpType::MODIFY: {
for (size_t i = 0; i < fmin(book_side.size(), 5); i++) {
if (book_side[i].price != 0 && book_side[i].price == op.price) {
//want to update quantity if price is a match
book_side[i].total_quantity = op.quantity;
break;
@@ -155,103 +157,113 @@ bool update_incremental(const IMarketDataListener::BookOp &op) {
}
break;
}
case IMarketDataListener::BookOpType::REMOVE:{
case IMarketDataListener::BookOpType::REMOVE: {
int found_position = -1;
for (size_t i = 0; i < fmin(book_side.size(),5); i++){
if(book_side[i].price == op.price){//trying to find based on price only
for (size_t i = 0; i < fmin(book_side.size(), 5); i++) {
if (book_side[i].price ==
op.price) { //trying to find based on price only
found_position = i;
break;
}
}
size_t level = found_position;
//would've had 4 here; want to be at last level
for(size_t i = found_position; i <fmin(book_side.size(),5); i++){
book_side[i] = book_side[i+1];
//would've had 4 here; want to be at last level
for (size_t i = found_position; i < fmin(book_side.size(), 5);
i++) {
book_side[i] = book_side[i + 1];
level++;
}
if(found_position != -1){
if (found_position != -1) {
//trying to set last level to 0
book_side[level].total_quantity =domain::Quantity(0);
book_side[level].total_quantity = domain::Quantity(0);
}
break;
}
default:
LOG(md,Error, "In the Default Case for IMarketDataListener::BookOp {}");
}
}else if (op.side == domain::Side::Sell){
switch(op.op_type){
case IMarketDataListener::BookOpType::Add:{
LOG(md, Error,
"In the Default Case for IMarketDataListener::BookOp {}");
}
} else if (op.side == domain::Side::Sell) {
switch (op.op_type) {
case IMarketDataListener::BookOpType::Add: {
int insert_position = 0;
for (int i = 0; i < fmin(book_side.size(),5); i++) {
if (book_side[i].price != 0 && book_side[i].price < op.price) {
insert_position++;
continue;
}
}//need i to be at the last level
for (int i = fmin(book_side.size()-1,5-1); i > insert_position; i--) {
book_side[i].price = book_side[i - 1].price;
book_side[i].total_quantity = book_side[i - 1].total_quantity;
for (int i = 0; i < fmin(book_side.size(), 5); i++) {
if (book_side[i].price != 0 && book_side[i].price < op.price) {
insert_position++;
continue;
}
} //need i to be at the last level
for (int i = fmin(book_side.size() - 1, 5 - 1); i > insert_position;
i--) {
book_side[i].price = book_side[i - 1].price;
book_side[i].total_quantity = book_side[i - 1].total_quantity;
}
book_side[insert_position].price = op.price;
book_side[insert_position].total_quantity = op.quantity;
book_side[insert_position].total_quantity = op.quantity;
break;
}
case IMarketDataListener::BookOpType::MODIFY:{
for (int i = 0; i < fmin(book_side.size(),5); i++) {
if (book_side[i].price != 0 && book_side[i].price == op.price) {
// For modify, we might want to update or keep the same value
book_side[i].price = op.price;
book_side[i].total_quantity = op.quantity;
break;
}
}break;
case IMarketDataListener::BookOpType::MODIFY: {
for (int i = 0; i < fmin(book_side.size(), 5); i++) {
if (book_side[i].price != 0 && book_side[i].price == op.price) {
// For modify, we might want to update or keep the same value
book_side[i].price = op.price;
book_side[i].total_quantity = op.quantity;
break;
}
}
break;
}
case IMarketDataListener::BookOpType::REMOVE: {
int found_position = -1;
for (size_t i = 0; i < fmin(book_side.size(),5); i++){
if(book_side[i].price == op.price){
for (size_t i = 0; i < fmin(book_side.size(), 5); i++) {
if (book_side[i].price == op.price) {
found_position = i;
break;
}
}
size_t level =found_position;
for (int i = found_position; i < fmin(book_side.size(),5); i++){
book_side[i].price = book_side[i+1].price;
book_side[i].total_quantity = book_side[i+1].total_quantity;
size_t level = found_position;
for (int i = found_position; i < fmin(book_side.size(), 5); i++) {
book_side[i].price = book_side[i + 1].price;
book_side[i].total_quantity = book_side[i + 1].total_quantity;
level++;
}
if(found_position != -1){
book_side[fmin(book_side.size()-1,level)].total_quantity = domain::Quantity(0);
if (found_position != -1) {
book_side[fmin(book_side.size() - 1, level)].total_quantity =
domain::Quantity(0);
}
break;
}
default:
LOG(md, Error, "In the Default Case for IMarketDataListener::BookOp {}");
LOG(md, Error,
"In the Default Case for IMarketDataListener::BookOp {}");
break;
}
}
/*---------------------*/
int counter = 0;
for (auto &level : book_side) {
if (counter > 4) {
level.total_quantity = domain::Quantity(0);
}
counter++;
}
/*---------------------*/
int counter =0;
for(auto &level:book_side){
if(counter >4){
level.total_quantity =domain::Quantity(0);
// Log final state
std::stringstream book_after;
book_after << "Book after update (" << side_str << " side):\n";
for (const auto &level : book_side) {
book_after << "Price: " << level.price
<< ", Quantity: " << level.total_quantity << "\n";
}
counter++;
std::string book_before_after_op =
book_before.str() + "\n" + book_after.str() + "\n" + op_details.str();
LOG(md, Diag, "Book Increment desc: {}", book_before_after_op);
bts_assert_msgf(
book->is_sorted(), "book not sorted {}", book_before_after_op);
return true;
}
// Log final state
std::stringstream book_after;
book_after << "Book after update (" << side_str << " side):\n";
for (const auto& level : book_side) {
book_after << "Price: " << level.price << ", Quantity: " << level.total_quantity << "\n";
}
std::string book_before_after_op = book_before.str() + "\n" + book_after.str() + "\n" + op_details.str();
LOG(md, Diag, "Book Increment desc: {}", book_before_after_op);
bts_assert_msgf(book->is_sorted(), "book not sorted {}", book_before_after_op);
return true;
}
domain::Book const *get_const_book(domain::SecurityID id) const {
auto it = book_map.find(id);
if (it == book_map.end()) {
@@ -301,8 +313,7 @@ struct GeneralMarketDataListener::PImpl {
GeneralSecurityIndex &index,
MDProducer &md_producer,
std::shared_ptr<bts::orderman::SimExecutionGateway> sim_ex_gwy)
: venue(venue), index(index),
md_producer(md_producer),
: venue(venue), index(index), md_producer(md_producer),
sim_ex_gwy(std::move(sim_ex_gwy)) {
if (this->sim_ex_gwy != nullptr) {
this->sim_ex_gwy->set_producers(&this->index);
@@ -464,7 +475,7 @@ void GeneralMarketDataListener::on_book(
void GeneralMarketDataListener::on_book_increment(
const IMarketDataListener::BookOp &book_op) {
if(!me->inc_book_data.update_incremental(book_op)){
if (!me->inc_book_data.update_incremental(book_op)) {
return;
}
domain::Book const *book_ptr = me->inc_book_data.get_const_book(book_op.id);
@@ -635,6 +646,81 @@ void GeneralMarketDataListener::_commit_security(const FixedSecurity &sec) {
handle.security().numeric_id, me->index.n_secs());
}
static bool get_qty_info(
double qty_incr, float &qty_tick_unit, float &qty_tick_base) {
float qty_tick_denom = 1.0 / qty_incr;
int qty_tick_denomi = static_cast<int>(qty_tick_denom);
if (qty_tick_denom != qty_tick_denomi) {
return false;
}
switch (static_cast<int>(qty_tick_denom)) {
case 1:
qty_tick_unit = 1;
qty_tick_base = 1;
break;
case 10:
qty_tick_unit = 10;
qty_tick_base = 100;
break;
case 100:
qty_tick_unit = 1;
qty_tick_base = 100;
break;
case 1000:
qty_tick_unit = 1;
qty_tick_base = 1000;
break;
case 10000:
qty_tick_unit = 1;
qty_tick_base = 10000;
break;
case 100000:
qty_tick_unit = 1;
qty_tick_base = 100000;
break;
case 1000000:
qty_tick_unit = 1;
qty_tick_base = 1000000;
break;
case 4:
qty_tick_unit = 25;
qty_tick_base = 100;
break;
case 40:
qty_tick_unit = 25;
qty_tick_base = 1000;
break;
case 400:
qty_tick_unit = 25;
qty_tick_base = 10000;
break;
case 4000:
qty_tick_unit = 25;
qty_tick_base = 100000;
break;
case 20:
qty_tick_unit = 5;
qty_tick_base = 100;
break;
case 200:
qty_tick_unit = 5;
qty_tick_base = 1000;
break;
case 2000:
qty_tick_unit = 5;
qty_tick_base = 10000;
break;
case 20000:
qty_tick_unit = 5;
qty_tick_base = 100000;
break;
default:
bts_assert_msg(false, "Invalid qty tick");
return false;
}
return true;
}
void GeneralMarketDataListener::_copy_security_common(
const bts::domain::Security &from, uint64_t key, FixedSecurity &to) const {
to.key = key;
@@ -648,6 +734,24 @@ void GeneralMarketDataListener::_copy_security_common(
to.display_factor = 0.0;
to.price_break = 0.0;
to.low_tick = to.high_tick = from.security_common.min_price_increment;
auto &sc = from.security_common;
double min_qty = sc.min_quantity;
double qty_incr = sc.min_quantity_increment;
if (!(min_qty > 0)) {
min_qty = qty_incr;
} else if (!(qty_incr > 0)) {
qty_incr = min_qty;
}
double max_qty = sc.max_quantity;
if (qty_incr > 0 && min_qty > 0 && max_qty > min_qty) {
float qty_tick_unit;
float qty_tick_base;
if (get_qty_info(qty_incr, qty_tick_unit, qty_tick_base)) {
to.add_quantity_info(
min_qty, max_qty, qty_tick_unit, qty_tick_base,
std::numeric_limits<float>::max(), false);
}
}
}
uint64_t GeneralMarketDataListener::_generate_security_key(
@@ -685,13 +789,16 @@ void GeneralMarketDataListener::_on_book(
_possibly_match_with_sim_orders(
bts::md::SecurityKey(book.security_id().value()));
LOG(md, Diag,
"GeneralMarketDataListener::_on_book post id: {} bid: {}/{}/{} ask: "
"GeneralMarketDataListener::_on_book sec: {} sec_id: {} handle_id: {} "
"bid: "
"{}/{}/{} ask: "
"{}/{}/{}",
handle->id, me->bids.at(0).price(), me->bids.at(0).direct_size(),
me->bids.at(0).size(), me->asks.at(0).price(),
me->asks.at(0).direct_size(), me->asks.at(0).size());
std::string(handle->handle.security().security_desc()),
book.security_id(), handle->id, me->bids.at(0).price(),
me->bids.at(0).direct_size(), me->bids.at(0).size(),
me->asks.at(0).price(), me->asks.at(0).direct_size(),
me->asks.at(0).size());
handle->handle.update_direct(time.value(), me->bids, me->asks);
}
} // namespace bts::md
//#endif
+2 -1
View File
@@ -1,7 +1,8 @@
#pragma once
#include "lib/md/SecurityIndex.h"
#include <cstdint>
#include <string>
namespace bts::md {
+3 -2
View File
@@ -1,8 +1,9 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/domain/Book.h"
#include "lib/domain/SecurityID.h"
#include "lib/md/PriceSize.h"
#include "lib/utils/TimeStamp.h"
#include <vector>
namespace bts::domain {
-3
View File
@@ -4,9 +4,6 @@
#include "lib/utils/ConcurrentPool.h"
#include "lib/utils/thread_manager.h"
#include <atomic>
#include <functional>
#include <map>
#include <sstream>
#include <thread>
#include <vector>
-4
View File
@@ -3,13 +3,9 @@
#include "IntPrice.h"
#include "MDProducerHandle.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/Security.h"
#include "lib/domain_model/SecurityKey.h"
#include "lib/utils/TimeStamp.h"
#include <btree/map.h>
#include <map>
#include <memory>
#include <tuple>
#include <vector>
-4
View File
@@ -1,6 +1,5 @@
#ifndef _WIN32
#include "MBOServiceServer.h"
#include "ExchangeOrderKey.h"
#include "MBOProcessor.h"
#include "MDClient.h"
@@ -8,11 +7,8 @@
#include "lib/domain_model/Security.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/TimeStamp.h"
#include "lib/utils/LockBox.h"
#include <tuple>
#include <unordered_map>
#include <vector>
+1 -1
View File
@@ -2,8 +2,8 @@
#ifndef _WIN32
#include "lib/api/public/ServiceContext.h"
#include "lib/api/services/MBOService.server.pb.h"
#include <memory>
#include <optional>
namespace bts {
namespace utils {
+1 -5
View File
@@ -3,16 +3,12 @@
#include "MBOProcessor.h"
#include "MemorySegmentUtils.h"
#include "SecurityCache.h"
#include "lib/config/config.h"
#include "lib/config/protos/market_data.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/logging/logging.h"
#include "lib/md/ShmSegmentName.h"
#include "lib/md/SymbolLoader.h"
#include "lib/utils/SharedList.h"
#include "lib/utils/WaitFreeQueue.h"
#include "lib/utils/bts_chrono.h"
#include "lib/utils/sane_algorithm.h"
#include <algorithm>
#include <btree/map.h>
+1 -3
View File
@@ -1,13 +1,11 @@
#pragma once
#include "lib/utils/bts_export.h"
#include "lib/utils/bts_no_copy.h"
#include "ISyntheticSecurityService.h"
#include "MDConsumerHandle.h"
#include "SecurityPtr.h"
#include "UserSymbol.h"
#include "common.h"
#include "lib/utils/bts_export.h"
#include <functional>
#include <google/protobuf/repeated_field.h>
+31 -155
View File
@@ -1,7 +1,11 @@
#ifndef NO_CPP0X
#include "MDConsumerHandle.h"
#include "MBOProcessor.h"
#include "lib/domain/BookSide.h"
#include "lib/domain/DomainSecurityUtils.h"
#include "lib/domain/synthetic_book_side.h"
#include "lib/domain_model/Security.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/os/os.h"
#include <boost/container/small_vector.hpp>
@@ -13,6 +17,10 @@ using small_vector = boost::container::small_vector<T, 4>;
namespace bts {
namespace md {
using bts::domain::DomainSecurityUtils;
using std::make_tuple;
using std::tie;
MDConsumerHandle::MDConsumerHandle() {
os::memset(this, 0, sizeof(MDConsumerHandle));
_type = HANDLETYPE_VOID;
@@ -102,125 +110,6 @@ TimeStamp copy_depth(
/////////////////////////////
// SYNTHETIC DEPTH
/////////////////////////////
struct Level {
double price;
double size;
};
struct DepthTraversal {
std::array<Level, COCONUT_MAX_DEPTH> levels;
size_t head;
api::Side side;
double qty, price_mult;
DepthTraversal(
api::Side side,
double qty,
double price_mult,
const PriceSize *depth,
size_t depth_count)
: levels(), head(0), side(side), qty(qty), price_mult(price_mult) {
// Copy prices into mutable storage
for (size_t x = 0; x < depth_count; ++x) {
levels[x].price = depth[x].price();
levels[x].size = depth[x].size();
}
}
double available_size() const {
double available = 0.0;
auto cur = head;
bool partial = false;
while (cur < levels.size() && available < 1) {
available += levels[cur].size / qty;
if (available < 1.0)
partial = true;
++cur;
}
if (partial && available >= 1.0)
return 1.0;
return available;
}
double consume(double size) {
bts_assert_d(size <= available_size());
double take = size * qty;
double price = 0.0;
while (take > 0) {
if (levels[head].size >= take) {
price += levels[head].price * price_mult * (take / qty);
levels[head].size -= take;
if (levels[head].size <= 0)
++head;
break;
} else {
double available = levels[head].size;
price += levels[head].price * price_mult * (available / qty);
levels[head].size = 0;
++head;
take -= available;
}
}
return price / size;
}
};
void copy_synthetic_side(
size_t count,
api::Side side,
const api::ContractSpecification &spec,
small_vector<DepthTraversal> &leg_travs,
Level *out_price) {
size_t out_index = 0;
bool first = true;
while (out_index < count) {
double price = 0.0;
// Find the leg with the minimum available size
double size = std::numeric_limits<double>().max();
for (const auto &dt : leg_travs) {
auto sz = dt.available_size();
// If we can't get 1.0 from any leg we're done
if (sz <= size)
size = sz;
if (size < 1.0)
break;
}
size = (int64_t)size;
if (size < 1.0) {
// We've run out of book so we nan out any remaining levels and finish
size_t start = first ? out_index : out_index + 1;
for (size_t x = start; x < count; ++x) {
out_price[x].price = os::nan();
out_price[x].size = 0;
}
return;
}
// Take that size from each leg
for (auto &dt : leg_travs) {
double temp = dt.consume(size);
price += temp;
}
bts_assert_d(!os::is_nan(price));
// Round to tick size
price = securities::checked_round_to_quote_price(spec, price, side);
if (first) {
first = false;
out_price[out_index].price = price;
}
if (price == out_price[out_index].price) {
out_price[out_index].size += size;
} else {
out_index += 1;
if (out_index < count) {
out_price[out_index].price = price;
out_price[out_index].size = size;
}
}
}
}
const spark::LegSpreaderParams &get_spreader_params(
const SyntheticStrategy &synth, security_key key) {
@@ -239,10 +128,11 @@ TimeStamp copy_synthetic_depth(
size_t count,
PriceSize *bid,
PriceSize *ask) {
small_vector<DepthTraversal> bid_leg_travs;
small_vector<DepthTraversal> ask_leg_travs;
bid_leg_travs.reserve(synth.legs.size());
ask_leg_travs.reserve(synth.legs.size());
domain::SyntheticBookSide synth_bids;
domain::SyntheticBookSide synth_asks;
// Store bid/ask for each leg (2 BookSides per leg).
std::vector<domain::BookSide> depth_cache;
depth_cache.resize(2 * synth.legs.size());
TimeStamp rv = std::numeric_limits<TimeStamp>::min();
for (size_t x = 0; x < synth.legs.size(); ++x) {
std::array<PriceSize, COCONUT_MAX_DEPTH> bids, asks;
@@ -258,41 +148,27 @@ TimeStamp copy_synthetic_depth(
}
if (r == HandleReadResult::BUSY || t == TimeStamp::invalid())
return TimeStamp::invalid();
// NET Change
if (synth.security.synthetic().price_calc() ==
api::SyntheticContract::PRICECALC_NET_CHANGE) {
auto settle = synth.legs[x].settlement_price();
for (size_t d = 0; d < COCONUT_MAX_DEPTH; ++d) {
bids[d] = {bids[d].int_price() - settle, bids[d].size()};
asks[d] = {asks[d].int_price() - settle, asks[d].size()};
}
}
rv = std::max(rv, t);
const auto &leg = synth.security.synthetic().legs(x);
const auto &eff_bid = leg.side() == api::SIDE_BUY ? bids : asks;
const auto &eff_ask = leg.side() == api::SIDE_BUY ? asks : bids;
bid_leg_travs.push_back(DepthTraversal(
leg.side(), leg.quantity(), leg.price_multiplier(), &eff_bid[0],
eff_bid.size()));
ask_leg_travs.push_back(DepthTraversal(
leg.side(), leg.quantity(), leg.price_multiplier(), &eff_ask[0],
eff_ask.size()));
}
std::array<Level, COCONUT_MAX_DEPTH> scratch{};
const auto &spec = synth.security.contract_spec();
copy_synthetic_side(
count, api::SIDE_BUY, spec, bid_leg_travs, scratch.data());
for (size_t x = 0; x < count; ++x) {
bid[x] = PriceSize_from_double<11>(scratch[x].price, scratch[x].size);
}
memset(scratch.data(), 0, sizeof(scratch));
copy_synthetic_side(
count, api::SIDE_SELL, spec, ask_leg_travs, scratch.data());
for (size_t x = 0; x < count; ++x) {
ask[x] = PriceSize_from_double<11>(scratch[x].price, scratch[x].size);
PriceSize *eff_bid, *eff_ask;
domain::Quantity rel_qty;
tie(eff_bid, eff_ask, rel_qty.value()) =
leg.side() == api::SIDE_BUY
? make_tuple(bids.data(), asks.data(), leg.quantity())
: make_tuple(asks.data(), bids.data(), -leg.quantity());
// Bid
domain::BookSide &cbids = depth_cache.at(x);
DomainSecurityUtils::convert(eff_bid, COCONUT_MAX_DEPTH, cbids);
synth_bids = synth_bids.add_leg({rel_qty, cbids.data(), cbids.size()});
// Ask
domain::BookSide &casks = depth_cache.at(x + synth.legs.size());
DomainSecurityUtils::convert(eff_ask, COCONUT_MAX_DEPTH, casks);
synth_asks = synth_asks.add_leg({rel_qty, casks.data(), casks.size()});
}
DomainSecurityUtils::convert(
synth_bids.data(), synth_bids.size(), bid, count);
DomainSecurityUtils::convert(
synth_asks.data(), synth_asks.size(), ask, count);
return rv;
}
+1 -1
View File
@@ -4,8 +4,8 @@
#include "lib/md/Shm/MDStructure.h"
#include "lib/sparkproto/SparkProtos.pb.h"
#include "lib/utils/bts_export.h"
#include "lib/utils/bts_no_copy.h"
#include "lib/utils/sane_algorithm.h"
#include <atomic>
#include <vector>
-1
View File
@@ -10,7 +10,6 @@
#include "lib/os/os.h"
#include "lib/utils/SharedList.h"
#include "lib/utils/WaitFreeQueue.h"
#include <iomanip>
using namespace std;
-2
View File
@@ -3,11 +3,9 @@
#include "MDProducerChannel.h"
#include "Shm/MDStructure.h"
#include "lib/utils/bts_export.h"
#include "lib/utils/bts_no_copy.h"
#include "lib/utils/datetime.h"
#include <tbb/concurrent_queue.h>
#include <vector>
namespace bts {
namespace co2 {
-2
View File
@@ -1,7 +1,5 @@
#ifndef NO_CPP0X
#include "MemorySegmentUtils.h"
#include "lib/config/config.h"
#include "lib/config/protos/market_data.pb.h"
using namespace std;
-8
View File
@@ -3,17 +3,9 @@
#include "IntPrice.h"
#include "SizeTypes.h"
#include "common.h"
#include "lib/os/os.h"
#include "lib/utils/Optimistic.h"
#include "lib/utils/bts_array_size.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/bts_no_copy.h"
#include "lib/utils/warn_ctrl.h"
#include <atomic>
#include <cmath>
#include <limits>
#include <nlohmann/json.hpp>
namespace bts {
-2
View File
@@ -5,11 +5,9 @@
#include "lib/logging/logging.h"
#include "lib/md/security_type_index.h"
#include "lib/md/venue_index.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/LockBox.h"
#include <btree/map.h>
#include <mutex>
#include <tbb/concurrent_queue.h>
#include <unordered_map>
+1 -1
View File
@@ -3,8 +3,8 @@
#include "MDConsumerHandle.h"
#include "SecurityPtr.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/SecurityKey.h"
#include "lib/md/ExchangeSecurityKey.h"
#include <memory>
namespace bts {
-5
View File
@@ -2,14 +2,9 @@
#include "SecurityPtr.h"
#include "UserSymbol.h"
#include "common.h"
#include "lib/api/services/SyntheticSecurityService.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/SecurityKey.h"
#include "lib/domain_model/common.h"
#include "lib/md/MDProducerHandle.h"
#include "lib/utils/ResultEnum.h"
#include "lib/utils/datetime.h"
#include <functional>
#include <set>
+3 -11
View File
@@ -1,20 +1,13 @@
#ifndef _WIN32
#include "SecurityIDRecorder.h"
#include "lib/domain_model/Security.h"
#include "lib/logging/logging.h"
#include "lib/persistence/IStore.h"
#include "lib/persistence/SqliteStore.h"
#include <boost/pool/object_pool.hpp>
#include "lib/config/config.h"
#include "lib/logging/logging.h"
#include "lib/utils/Sqlite_utils.h"
#include "lib/utils/checked_arithmetic.h"
#include <boost/filesystem.hpp>
#include <tbb/concurrent_queue.h>
#include "lib/config/protos/market_data.pb.h"
#include "lib/config/protos/system_config.pb.h"
#include <boost/pool/object_pool.hpp>
#include <memory>
#include <sqlite/sqlite3.h>
#include <stdexcept>
@@ -31,7 +24,6 @@ namespace md {
using bts::api::Security;
using bts::api::SecurityType;
using bts::api::Venue;
using bts::config::SystemConfig;
using bts::persistence::IStore;
using bts::persistence::SqliteStore;
+1 -2
View File
@@ -1,10 +1,9 @@
#pragma once
#ifndef _WIN32
#include "MDProducerHandle.h"
#include "common.h"
#include "lib/persistence/IStore.h"
#include "lib/utils/Sqlite_utils.h"
#include <atomic>
#include <functional>
#include <memory>
#include <optional>
+1 -2
View File
@@ -3,14 +3,13 @@
#include "MDProducerHandle.h"
#include "common.h"
#include "lib/logging/logging.h"
#include "lib/md/Shm/MDStructure.h"
#include "lib/utils/bts_no_copy.h"
#include <memory>
#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_queue.h>
#include <tbb/queuing_rw_mutex.h>
#include <unordered_map>
#include <vector>
namespace bts {
namespace md {
-1
View File
@@ -5,7 +5,6 @@
#include "lib/config/config.h"
#include "lib/config/protos/cme_reference_data.pb.h"
#include "lib/config/protos/market_data.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/utils/parse_enum.h"
#ifndef _WIN32
+1 -2
View File
@@ -1,6 +1,5 @@
#pragma once
#include "lib/domain_model/SecurityKey.h"
#include <memory>
namespace bts {
@@ -12,4 +11,4 @@ namespace md {
typedef std::shared_ptr<const api::Security> SecurityPtr;
}
} // namespace bts
} // namespace bts
-3
View File
@@ -1,13 +1,10 @@
#include "SecurityStore.h"
#include "lib/domain/FutureSecurity.h"
#include "lib/domain/Security.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/LockBox.h"
#include <btree/map.h>
#include <mutex>
#include <tbb/concurrent_queue.h>
#include <unordered_map>
namespace bts::md {
+1 -1
View File
@@ -1,9 +1,9 @@
#pragma once
#include "MDConsumerHandle.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/domain/SecurityID.h"
#include <memory>
namespace bts::domain {
+5 -2
View File
@@ -13,10 +13,13 @@
namespace bts {
namespace co2 {
using bts::md::INT_NAN;
using bts::md::IntPrice;
using bts::md::invalid_security_key;
using bts::md::MDProducerHandle;
using bts::md::security_key;
using datetime::Date;
using std::string;
using std::unique_ptr;
using namespace utils;
enum WorkType { SNAPSHOT, UPDATE };
+4 -4
View File
@@ -1,12 +1,12 @@
#pragma once
#include "MDProducerHandle.h"
#include "lib/utils/bts_export.h"
#include "lib/utils/datetime.h"
#include <memory>
namespace bts {
namespace co2 {
using namespace md;
struct SettlementRecorderImpl;
class SettlementRecorder {
@@ -16,9 +16,9 @@ class SettlementRecorder {
// Put settlement update in a queue
// in do_work() drain queue and update settlement 'depth'
void restore_settlement(MDProducerHandle handle);
void restore_settlement(md::MDProducerHandle handle);
void add_settlement(
MDProducerHandle handle, IntPrice price, datetime::Date date);
md::MDProducerHandle handle, md::IntPrice price, datetime::Date date);
void do_work();
private:
+82 -1
View File
@@ -1,4 +1,5 @@
#include "lib/domain_model/Security.h"
#include "lib/utils/bts_array_size.h"
#include "lib/utils/bts_assert.h"
#ifndef NO_CPP0X
@@ -25,7 +26,7 @@ FixedSecurity::FixedSecurity()
cme_main_fraction(0), cme_sub_fraction(0), cme_price_display_format(0),
ice_price_denominator(0), ice_deal_denominator(0),
ice_settle_denominator(0), is_ice_strip(false), _symbol(), _cfi_code(),
_security_desc(), _alt_symbol() {
_security_desc(), _alt_symbol(), _qty_info_size(0) {
memset(_symbol, 0, BTS_ARRAYSIZE(_symbol));
memset(_alt_symbol, 0, BTS_ARRAYSIZE(_alt_symbol));
memset(_cfi_code, 0, BTS_ARRAYSIZE(_cfi_code));
@@ -39,10 +40,57 @@ FixedSecurity::FixedSecurity()
memset(_ice_hub_alias, 0, BTS_ARRAYSIZE(_ice_hub_alias));
memset(_ice_strip_name, 0, BTS_ARRAYSIZE(_ice_strip_name));
memset(_mic_code, 0, BTS_ARRAYSIZE(_mic_code));
memset(_qty_min_value, 0, BTS_ARRAYSIZE_T(_qty_min_value));
memset(_qty_max_value, 0, BTS_ARRAYSIZE_T(_qty_max_value));
memset(_qty_tick_unit, 0, BTS_ARRAYSIZE_T(_qty_tick_unit));
memset(_qty_tick_base, 0, BTS_ARRAYSIZE_T(_qty_tick_base));
memset(_qty_multiplier, 0, BTS_ARRAYSIZE_T(_qty_multiplier));
memset(_qty_max_price, 0, BTS_ARRAYSIZE_T(_qty_max_price));
memset(_qty_is_notional, 0, BTS_ARRAYSIZE_T(_qty_is_notional));
}
FixedSecurity::~FixedSecurity() {}
bool FixedSecurity::add_quantity_info(
float min_value,
float max_value,
float tick_unit,
float tick_base,
float max_price,
bool is_notional) {
if (_qty_info_size >= QTY_INFO_CAPACITY) {
return false;
}
_qty_min_value[_qty_info_size] = min_value;
_qty_max_value[_qty_info_size] = max_value;
_qty_tick_unit[_qty_info_size] = tick_unit;
_qty_tick_base[_qty_info_size] = tick_base;
_qty_max_price[_qty_info_size] = max_price;
_qty_is_notional[_qty_info_size] = is_notional;
_qty_info_size++;
return true;
}
bool FixedSecurity::get_quantity_info(
uint8_t index,
float &min_value,
float &max_value,
float &tick_unit,
float &tick_base,
float &max_price,
bool &is_notional) const {
if (index >= _qty_info_size) {
return false;
}
min_value = _qty_min_value[index];
max_value = _qty_max_value[index];
tick_unit = _qty_tick_unit[index];
tick_base = _qty_tick_base[index];
max_price = _qty_max_price[index];
is_notional = _qty_is_notional[index];
return true;
}
FixedFutureSecurity::FixedFutureSecurity()
: expiration_year(0), expiration_month(0), expiration_day(0) {
type = api::SECURITYTYPE_FUTURE;
@@ -75,6 +123,29 @@ void get_contract_spec(
spec.set_main_fraction(source.cme_main_fraction);
spec.set_sub_fraction(source.cme_sub_fraction);
spec.set_price_display_format(source.cme_price_display_format);
// Qty info
spec.clear_quantity_infos();
spec.clear_notional_quantity_infos();
float min_value;
float max_value;
float tick_unit;
float tick_base;
float max_price;
bool is_notional;
for (uint8_t i = 0; source.get_quantity_info(
i, min_value, max_value, tick_unit, tick_base, max_price,
is_notional);
i++) {
ContractQuantityInfo *qty_info = is_notional
? spec.add_notional_quantity_infos()
: spec.add_quantity_infos();
qty_info->set_min_value(min_value);
qty_info->set_max_value(max_value);
qty_info->set_tick_unit(tick_unit);
qty_info->set_tick_base(tick_base);
qty_info->set_max_price(max_price);
}
}
Security convert_security(const FixedSecurity &security) {
@@ -365,6 +436,16 @@ void convert_security(
dest.cme_sub_fraction = src.contract_spec().sub_fraction();
dest.cme_price_display_format = src.contract_spec().price_display_format();
for (const auto &q : src.contract_spec().quantity_infos()) {
bts_assert(dest.add_quantity_info(
q.min_value(), q.max_value(), q.tick_unit(), q.tick_base(),
q.max_price(), false));
}
for (const auto &q : src.contract_spec().notional_quantity_infos()) {
bts_assert(dest.add_quantity_info(
q.min_value(), q.max_value(), q.tick_unit(), q.tick_base(),
q.max_price(), true));
}
switch (securitytype_of(src)) {
case SECURITYTYPE_FUTURE: {
FixedFutureSecurity &future = static_cast<FixedFutureSecurity &>(dest);
+33
View File
@@ -85,6 +85,25 @@ class BTS_EXPORT FixedSecurity {
STRING_PROPERTY(ice_strip_name);
STRING_PROPERTY(mic_code);
static constexpr const uint8_t QTY_INFO_CAPACITY = 2;
bool add_quantity_info(
float min_value,
float max_value,
float tick_unit,
float tick_base,
float max_price,
bool is_notional);
bool get_quantity_info(
uint8_t index,
float &min_value,
float &max_value,
float &tick_unit,
float &tick_base,
float &max_price,
bool &is_notional) const;
private:
char _symbol[16]; // CME defines this as a char[6] but I'm padding
char _cfi_code[8]; // CME defines this as char[6]
@@ -100,6 +119,20 @@ class BTS_EXPORT FixedSecurity {
char _ice_hub_alias[80];
char _ice_strip_name[39];
char _mic_code[4];
// The '_qty' arrays collectively represent a list of "ContractQuantityInfo"
// in Security.proto, with a caveat for '_qty_is_notional'.
float _qty_min_value[QTY_INFO_CAPACITY];
float _qty_max_value[QTY_INFO_CAPACITY];
float _qty_tick_unit[QTY_INFO_CAPACITY];
float _qty_tick_base[QTY_INFO_CAPACITY];
float _qty_multiplier[QTY_INFO_CAPACITY];
float _qty_max_price[QTY_INFO_CAPACITY];
// In the proto, each security has 2 "ContractQuantityInfo" lists: quantity_infos and
// notional_quantity_infos. This indicates the list to which each element belongs.
bool _qty_is_notional[QTY_INFO_CAPACITY];
// The number of qty info elements defined in the above arrays.
uint8_t _qty_info_size;
};
class FixedSpotSecurity : public FixedSecurity {
+2 -1
View File
@@ -1,8 +1,9 @@
#pragma once
#include "lib/config/config.h"
#include "lib/config/protos/market_data.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/md/protos/co2.pb.h"
#include <fmt/format.h>
#include <string>
+6 -2
View File
@@ -1,7 +1,11 @@
#pragma once
#include "lib/utils/ValueWrapper.h"
#include <cstdint>
namespace bts {
namespace md {
using ImpliedSize = utils::ValueWrapper<struct ImpliedSizeTag, uint32_t>;
using ImpliedSize = utils::ValueWrapper<struct ImpliedSizeTag, std::uint32_t>;
}
} // namespace bts
} // namespace bts
+1 -5
View File
@@ -1,13 +1,9 @@
#pragma once
#include "UserSymbol.h"
#include "common.h"
#include "lib/api/md/MarketDataConfig.pb.h"
#include "lib/collections/Contains.h"
#include "lib/config/config.h"
#include "lib/config/protos/market_data.pb.h"
#include "lib/config/protos/symbols.pb.h"
#include "lib/core/FMT.h"
#include "lib/domain/DomainSecurityUtils.h"
#include "lib/domain/SecurityCriteria.h"
#include "lib/domain_model/Security.pb.h"
-7
View File
@@ -1,19 +1,12 @@
#ifndef _WIN32
#include "SyntheticSecurityService.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/api/public/ServiceContext.h"
#include "lib/api/services/PersistenceService.pb.h"
#include "lib/config/config.h"
#include "lib/domain/SecurityCriteria.h"
#include "lib/md/SecurityDB.h"
#include "lib/md/SecurityIDRecorder.h"
#include "lib/md/SymbolLoader.h"
#include "lib/md/common.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/Event.h"
#include <set>
namespace bts {
namespace api {
+2 -1
View File
@@ -1,9 +1,10 @@
#pragma once
#include "ISyntheticSecurityService.h"
#include "lib/api/services/SyntheticSecurityService.pb.h"
#include "lib/api/services/SyntheticSecurityService.server.pb.h"
#include "lib/domain_model/SecurityKey.h"
#include "lib/md/common.h"
#include <memory>
#include <unordered_map>
+2 -1
View File
@@ -1,7 +1,8 @@
#pragma once
#include "common.h"
#include "lib/core/FMT_IMPL.h"
#include <sstream>
#include <string>
namespace bts {
+1 -1
View File
@@ -1,9 +1,9 @@
#include "lib/domain/DomainSecurityUtils.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/utils/bts_assert.h"
#ifndef _WIN32
#include "QueryServer.h"
#include "lib/config/config.h"
#include "lib/domain_model/Security.h"
#include "lib/md/SymbolLoader.h"
#include "lib/utils/AuthToken.h"
#include "lib/utils/Dispatcher.h"
+4 -8
View File
@@ -2,22 +2,18 @@
#include "RelayRecorder.h"
#include "lib/config/config.h"
#include "lib/config/protos/market_data.pb.h"
#include "lib/config/protos/system_config.pb.h"
#include "lib/logging/logging.h"
#include "lib/md/SecurityIDRecorder.h"
#include "lib/md/protos/co2.pb.h"
#include "lib/utils/HTTPStream.h"
#include "lib/utils/bts_chrono.h"
#include "lib/utils/datetime.h"
#include "lib/config/protos/market_data.pb.h"
#include "lib/config/protos/system_config.pb.h"
#include "lib/md/SecurityIDRecorder.h"
#include "lib/md/common.h"
#include "lib/md/protos/co2.pb.h"
#include "lib/utils/bts_chrono.h"
#include <boost/filesystem.hpp>
#include <google/protobuf/text_format.h>
#include <memory>
#include <string.h>
#include <thread>
using namespace std;
+305
View File
@@ -0,0 +1,305 @@
#include "MDSource.h"
#include "lib/domain_model/Security.h"
#include "lib/md/cme/MDP3Utils.h"
namespace bts::orderman {
MDSource::MDSource(
md::MDConsumer &consumer,
metrics::MetricsContainer &stats,
trace::PerfLog &log,
md::TradeSummarySignalDispatcher *trade_signal_dispatcher)
: _handles(), _trade_signal_dispatcher(trade_signal_dispatcher),
_pending_handles(), _status_subs(), _md_consumer(consumer),
_book_update(), _ticker_update(), _status_update(), _perf_log(log),
_stats(stats), _loop_timer(), _counter(0) {
_trades_buffer.Reserve(1000);
auto cme_channels = md::get_configured_cme_channels();
for (auto const &c : cme_channels)
for (auto const &s : c.symbols)
_symbol_to_channel[s] = c.id;
}
void MDSource::init() {
LOG(OrderMan, Warn, "Starting MDSource actor");
// TODO: why is this hitting?
bts_assert_msg(
_md_consumer.all_heartbeats_ok(),
"Marketdata heartbeats are bad. MDConsumer will not recover from "
"this condition. Please run 'sparkd md cleanup' and restart sparkd");
std::array<double, 30> buckets;
auto i = 1;
for (auto &b : buckets)
b = (i *= 2);
_loop_timer = std::make_shared<metrics::HistogramMetric<30>>(
"spark_metrics_md_loop_us", buckets);
_stats.publish(_loop_timer);
auto security_count_metric = std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_security_count");
auto synthetic_count_metric = std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_synthetic_count");
auto ticker_channel_cout_metric =
std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_ticker_channel_count");
auto status_count_metric = std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_status_count");
_stats.publish(security_count_metric);
_stats.publish(synthetic_count_metric);
_stats.publish(ticker_channel_cout_metric);
_stats.publish(status_count_metric);
_book_update = make_stream<BookUpdateEvent>();
_trade_signal_update = make_stream<TradeSignalEvent>();
_ticker_update = make_stream<TickerUpdateEvent>();
_status_update = make_stream<StatusUpdateEvent>();
subscribe<MDRequestEvent>(
[this, security_count_metric, synthetic_count_metric,
ticker_channel_cout_metric, status_count_metric](auto const &update) {
if (update.events) {
for (auto const &ev : *update.events) {
auto k = ev.security_key;
switch (ev.md_type) {
case MDRequestType::BOOK:
on_book_sub(k, *synthetic_count_metric);
security_count_metric->set(_handles.size());
break;
case MDRequestType::TICKER:
on_ticker_sub(k, *ticker_channel_cout_metric);
break;
case MDRequestType::STATUS:
on_status_sub(k);
status_count_metric->set(_status_subs.size());
break;
}
}
}
});
security_count_metric->set(_handles.size());
}
void MDSource::on_poll(trace::PerfLog &log) {
auto scoped_do_work = trace::log_scope(
log, {}, "MDSource::on_poll",
utils::TimeStamp::Interval::from_microseconds(2));
// Book Updates
Book book;
auto start = utils::TimeStamp::now();
for (auto &h : _handles) {
auto &handle = h.handle;
if (handle.get_book(
h.last, std::tuple_size<BookSide>(), &book.bid()[0],
&book.ask()[0]) == md::HandleReadResult::SUCCESS) {
book._security_key = SecurityKey(handle.key());
trace::CorID trace{
h.external_event_channel, handle.sequence(), h.last};
_perf_log.log(trace, h.last, "md", handle.key());
_perf_log.log(trace, "md_r", h.last, h.symbol->c_str(), handle.key());
publish(_book_update, book, trace);
}
if (_trade_signal_dispatcher) {
auto tsignal = _trade_signal_dispatcher->poll(handle.key());
if (tsignal.ts > h.last_trade_signal && tsignal.ts > h.last) {
h.last_trade_signal = tsignal.ts;
trace::CorID trace{
h.external_event_channel, tsignal.packet_sn, tsignal.ts};
publish(_trade_signal_update, tsignal, trace);
}
}
}
// Ticker Updates
for (auto &c : _channels) {
auto &channel = c.channel;
_trades_buffer.Clear();
c.cursor = channel.read_trades(_trades_buffer, c.cursor);
for (auto const &trade : _trades_buffer) {
size_t trade_count = 0;
for (auto &per_sec_ev : c.securities) {
if (trade.key() == per_sec_ev.key().value()) {
trade_count++;
per_sec_ev.trades.emplace_back(Trade{
datetime::utc_ll_to_timepoint(trade.time()),
Price(
md::IntPrice(trade.mantissa(), (int8_t)trade.exponent())
.price()),
Quantity(trade.size()), Quantity(trade.total_volume()),
trade.aggressor_side()});
}
}
if (trade_count)
for (auto &per_sec_ev : c.securities)
if (!per_sec_ev.trades.empty()) {
publish(_ticker_update, per_sec_ev);
per_sec_ev.trades.clear();
}
}
}
// Status updates
for (auto &s : _status_subs) {
auto status = s.handle.trading_status();
if (status != s.status) {
s.status = status;
publish(_status_update, SecurityKey{s.handle.key()}, status);
}
}
// Publish stats
auto stop = utils::TimeStamp::now();
_loop_timer->observe((start - stop).nanoseconds());
_counter += 1;
if (_counter % (2 << 20) == 0) {
// Invoke periodic work every 2M calls on on_poll
// This is approximately 20-30 seconds
do_periodic_work();
}
}
void MDSource::do_periodic_work() {
if (!_pending_handles.empty()) {
for (auto iter = _pending_handles.begin(); iter != _pending_handles.end();
++iter) {
if (iter->handle->is_initialized()) {
auto handle = *(iter->handle);
LOG(OrderMan, Info, "Found md for {}. Pending handle removed.",
handle.key());
switch (iter->type) {
case MDRequestType::BOOK:
_handles.push_back(
{utils::TimeStamp(), utils::TimeStamp(), handle,
get_external_event_channel(handle.get_usersymbol()),
std::make_unique<std::string>(securities::descriptor(
handle.get_normalized_security()))});
break;
case MDRequestType::STATUS:
for (auto &h : _status_subs) {
// no need to reset status
if (h.handle.key() == handle.key())
break;
}
_status_subs.push_back(
StatusRecord{handle, co2::TRADINGSTATUS_UNKNOWN});
case MDRequestType::TICKER:
// We aren't currently monitoring for pending ticker requests
break;
}
}
}
_pending_handles.erase(
std::remove_if(
_pending_handles.begin(), _pending_handles.end(),
[](const auto &h) { return h.handle->is_initialized(); }),
_pending_handles.end());
}
}
void MDSource::on_book_sub(
SecurityKey sk, metrics::CounterMetric<int> &synthetic_count_metric) {
auto k = sk.value();
for (auto &h : _handles)
if (h.handle.key() == k) {
// we want to resend this MD so that late joiners can
// catch up
h.last = utils::TimeStamp();
return;
}
LOG(OrderMan, Warn, "Starting to publish book updates for security key: {}",
k);
auto mhandle = _md_consumer.get_managed_handle(k);
if (mhandle->is_initialized()) {
auto handle = *mhandle;
_handles.push_back(
{utils::TimeStamp(), utils::TimeStamp(), handle,
get_external_event_channel(handle.get_usersymbol()),
std::make_unique<std::string>(
securities::descriptor(handle.get_normalized_security()))});
if (handle.is_synthetic())
synthetic_count_metric.increment();
} else {
// A pending handle is a handle for security we are reasonbly sure
// exists but we haven't yet found live market data for.
LOG(OrderMan, Warn, "No market data for {}. Pending handle added.", k);
_pending_handles.push_back({mhandle, MDRequestType::BOOK});
}
}
void MDSource::on_ticker_sub(
SecurityKey sk, metrics::CounterMetric<int> &channel_count_metric) {
auto handle = _md_consumer.get_managed_handle(sk.value());
if (!handle->is_initialized()) {
LOG(OrderMan, Warn,
"Received ticker subscription for security {} which does not have "
"valid market data. Ignoring",
sk.value());
return;
}
auto user_symbol = handle->get_usersymbol();
for (auto &ch : _channels) {
if (ch.user_symbol == user_symbol) {
auto it = std::find_if(
ch.securities.begin(), ch.securities.end(),
[sk](auto const &ev) { return ev.key() == sk; });
if (it == ch.securities.end()) {
// first time we are asked for this security, we will
// publish the trades for it next time around
LOG(OrderMan, Warn,
"Starting to publish ticker updates on user symbol {} for "
"security key: {} (sec count for the symbol: {} )",
user_symbol.to_string(), sk.value(), ch.securities.size() + 1);
ch.securities.push_back(sk);
}
return;
}
}
// this is a channel we are not subscribed to
channel_count_metric.increment();
LOG(OrderMan, Warn,
"Starting to publish ticker updates on user symbol {} for security "
"key: {}",
user_symbol.to_string(), sk.value());
// We do not want to get old trades so we drain the trades buffer at
// this point. This makes the first subscription to a channel
// consistent with subsquent ones in terms of late join semantics.
ChannelRecord c{0, _md_consumer.get_channel(user_symbol), {}, user_symbol};
_trades_buffer.Clear();
c.cursor = c.channel.read_trades(_trades_buffer, c.cursor);
c.securities.push_back(sk);
_channels.push_back(c);
}
void MDSource::on_status_sub(SecurityKey sk) {
auto k = sk.value();
for (auto &h : _status_subs) {
if (h.handle.key() == k) {
// reset status so it get resent next on_poll
h.status = co2::TRADINGSTATUS_UNKNOWN;
return;
}
}
auto handle = _md_consumer.get_managed_handle(k);
if (handle->is_initialized()) {
_status_subs.push_back(StatusRecord{*handle, co2::TRADINGSTATUS_UNKNOWN});
} else {
LOG(OrderMan, Warn, "No market data for {}. Pending handle added.", k);
_pending_handles.push_back({handle, MDRequestType::STATUS});
}
}
} // namespace bts::orderman
+8 -299
View File
@@ -1,14 +1,9 @@
#pragma once
#include "lib/domain_model/Security.h"
#include "lib/logging/logging.h"
#include "lib/md/IntPrice.h"
#include "lib/md/MDConsumer.h"
#include "lib/md/MDConsumerHandle.h"
#include "lib/md/cme/MDP3Utils.h"
#include "lib/mercury/actors/Actor.h"
#include "lib/mercury/actors/IPollableActor.h"
#include "lib/mercury/events/TimerEvent.h"
#include "lib/metrics/metrics.h"
#include "lib/orderman/events/BookUpdateEvent.h"
#include "lib/orderman/events/MDRequestEvent.h"
@@ -34,239 +29,17 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
md::MDConsumer &consumer,
metrics::MetricsContainer &stats,
trace::PerfLog &log,
md::TradeSummarySignalDispatcher *trade_signal_dispatcher)
: _handles(), _trade_signal_dispatcher(trade_signal_dispatcher),
_pending_handles(), _status_subs(), _md_consumer(consumer),
_book_update(), _ticker_update(), _status_update(), _perf_log(log),
_stats(stats), _loop_timer(), _counter(0) {
md::TradeSummarySignalDispatcher *trade_signal_dispatcher);
_trades_buffer.Reserve(1000);
auto cme_channels = md::get_configured_cme_channels();
for (auto const &c : cme_channels)
for (auto const &s : c.symbols)
_symbol_to_channel[s] = c.id;
}
void init() override {
LOG(OrderMan, Warn, "Starting MDSource actor");
// TODO: why is this hitting?
bts_assert_msg(
_md_consumer.all_heartbeats_ok(),
"Marketdata heartbeats are bad. MDConsumer will not recover from "
"this condition. Please run 'sparkd md cleanup' and restart sparkd");
std::array<double, 30> buckets;
auto i = 1;
for (auto &b : buckets)
b = (i *= 2);
_loop_timer = std::make_shared<metrics::HistogramMetric<30>>(
"spark_metrics_md_loop_us", buckets);
_stats.publish(_loop_timer);
auto security_count_metric =
std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_security_count");
auto synthetic_count_metric =
std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_synthetic_count");
auto ticker_channel_cout_metric =
std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_ticker_channel_count");
auto status_count_metric = std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_status_count");
_stats.publish(security_count_metric);
_stats.publish(synthetic_count_metric);
_stats.publish(ticker_channel_cout_metric);
_stats.publish(status_count_metric);
_book_update = make_stream<BookUpdateEvent>();
_trade_signal_update = make_stream<TradeSignalEvent>();
_ticker_update = make_stream<TickerUpdateEvent>();
_status_update = make_stream<StatusUpdateEvent>();
subscribe<MDRequestEvent>([this, security_count_metric,
synthetic_count_metric,
ticker_channel_cout_metric,
status_count_metric](auto const &update) {
if (update.events) {
for (auto const &ev : *update.events) {
auto k = ev.security_key;
switch (ev.md_type) {
case MDRequestType::BOOK:
on_book_sub(k, *synthetic_count_metric);
security_count_metric->set(_handles.size());
break;
case MDRequestType::TICKER:
on_ticker_sub(k, *ticker_channel_cout_metric);
break;
case MDRequestType::STATUS:
on_status_sub(k);
status_count_metric->set(_status_subs.size());
break;
}
}
}
});
security_count_metric->set(_handles.size());
}
void init() override;
private:
void on_poll(trace::PerfLog &log) override {
void on_poll(trace::PerfLog &log) override;
auto scoped_do_work = trace::log_scope(
log, {}, "MDSource::on_poll",
utils::TimeStamp::Interval::from_microseconds(2));
// Book Updates
Book book;
auto start = utils::TimeStamp::now();
for (auto &h : _handles) {
auto &handle = h.handle;
if (handle.get_book(
h.last, std::tuple_size<BookSide>(), &book.bid()[0],
&book.ask()[0]) == md::HandleReadResult::SUCCESS) {
book._security_key = SecurityKey(handle.key());
trace::CorID trace{
h.external_event_channel, handle.sequence(), h.last};
_perf_log.log(trace, h.last, "md", handle.key());
_perf_log.log(
trace, "md_r", h.last, h.symbol->c_str(), handle.key());
publish(_book_update, book, trace);
}
if (_trade_signal_dispatcher) {
auto tsignal = _trade_signal_dispatcher->poll(handle.key());
if (tsignal.ts > h.last_trade_signal && tsignal.ts > h.last) {
h.last_trade_signal = tsignal.ts;
trace::CorID trace{
h.external_event_channel, tsignal.packet_sn, tsignal.ts};
publish(_trade_signal_update, tsignal, trace);
}
}
}
// Ticker Updates
for (auto &c : _channels) {
auto &channel = c.channel;
_trades_buffer.Clear();
c.cursor = channel.read_trades(_trades_buffer, c.cursor);
for (auto const &trade : _trades_buffer) {
size_t trade_count = 0;
for (auto &per_sec_ev : c.securities) {
if (trade.key() == per_sec_ev.key().value()) {
trade_count++;
per_sec_ev.trades.emplace_back(Trade{
datetime::utc_ll_to_timepoint(trade.time()),
Price(md::IntPrice(
trade.mantissa(), (int8_t)trade.exponent())
.price()),
Quantity(trade.size()), Quantity(trade.total_volume()),
trade.aggressor_side()});
}
}
if (trade_count)
for (auto &per_sec_ev : c.securities)
if (!per_sec_ev.trades.empty()) {
publish(_ticker_update, per_sec_ev);
per_sec_ev.trades.clear();
}
}
}
// Status updates
for (auto &s : _status_subs) {
auto status = s.handle.trading_status();
if (status != s.status) {
s.status = status;
publish(_status_update, SecurityKey{s.handle.key()}, status);
}
}
// Publish stats
auto stop = utils::TimeStamp::now();
_loop_timer->observe((start - stop).nanoseconds());
_counter += 1;
if (_counter % (2 << 20) == 0) {
// Invoke periodic work every 2M calls on on_poll
// This is approximately 20-30 seconds
do_periodic_work();
}
}
void do_periodic_work() {
if (!_pending_handles.empty()) {
for (auto iter = _pending_handles.begin();
iter != _pending_handles.end(); ++iter) {
if (iter->handle->is_initialized()) {
auto handle = *(iter->handle);
LOG(OrderMan, Info, "Found md for {}. Pending handle removed.",
handle.key());
switch (iter->type) {
case MDRequestType::BOOK:
_handles.push_back(
{utils::TimeStamp(), utils::TimeStamp(), handle,
get_external_event_channel(handle.get_usersymbol()),
std::make_unique<std::string>(securities::descriptor(
handle.get_normalized_security()))});
break;
case MDRequestType::STATUS:
for (auto &h : _status_subs) {
// no need to reset status
if (h.handle.key() == handle.key())
break;
}
_status_subs.push_back(
StatusRecord{handle, co2::TRADINGSTATUS_UNKNOWN});
case MDRequestType::TICKER:
// We aren't currently monitoring for pending ticker requests
break;
}
}
}
_pending_handles.erase(
std::remove_if(
_pending_handles.begin(), _pending_handles.end(),
[](const auto &h) { return h.handle->is_initialized(); }),
_pending_handles.end());
}
}
void do_periodic_work();
void on_book_sub(
SecurityKey sk, metrics::CounterMetric<int> &synthetic_count_metric) {
auto k = sk.value();
for (auto &h : _handles)
if (h.handle.key() == k) {
// we want to resend this MD so that late joiners can
// catch up
h.last = utils::TimeStamp();
return;
}
LOG(OrderMan, Warn,
"Starting to publish book updates for security key: {}", k);
auto mhandle = _md_consumer.get_managed_handle(k);
if (mhandle->is_initialized()) {
auto handle = *mhandle;
_handles.push_back(
{utils::TimeStamp(), utils::TimeStamp(), handle,
get_external_event_channel(handle.get_usersymbol()),
std::make_unique<std::string>(
securities::descriptor(handle.get_normalized_security()))});
if (handle.is_synthetic())
synthetic_count_metric.increment();
} else {
// A pending handle is a handle for security we are reasonbly sure
// exists but we haven't yet found live market data for.
LOG(OrderMan, Warn, "No market data for {}. Pending handle added.", k);
_pending_handles.push_back({mhandle, MDRequestType::BOOK});
}
}
SecurityKey sk, metrics::CounterMetric<int> &synthetic_count_metric);
const char *get_external_event_channel(const md::UserSymbol &user_symbol) {
auto &channel = _symbol_to_channel[user_symbol];
@@ -276,73 +49,9 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
}
void on_ticker_sub(
SecurityKey sk, metrics::CounterMetric<int> &channel_count_metric) {
auto handle = _md_consumer.get_managed_handle(sk.value());
SecurityKey sk, metrics::CounterMetric<int> &channel_count_metric);
if (!handle->is_initialized()) {
LOG(OrderMan, Warn,
"Received ticker subscription for security {} which does not have "
"valid market data. Ignoring",
sk.value());
return;
}
auto user_symbol = handle->get_usersymbol();
for (auto &ch : _channels) {
if (ch.user_symbol == user_symbol) {
auto it = std::find_if(
ch.securities.begin(), ch.securities.end(),
[sk](auto const &ev) { return ev.key() == sk; });
if (it == ch.securities.end()) {
// first time we are asked for this security, we will
// publish the trades for it next time around
LOG(OrderMan, Warn,
"Starting to publish ticker updates on user symbol {} for "
"security key: {} (sec count for the symbol: {} )",
user_symbol.to_string(), sk.value(),
ch.securities.size() + 1);
ch.securities.push_back(sk);
}
return;
}
}
// this is a channel we are not subscribed to
channel_count_metric.increment();
LOG(OrderMan, Warn,
"Starting to publish ticker updates on user symbol {} for security "
"key: {}",
user_symbol.to_string(), sk.value());
// We do not want to get old trades so we drain the trades buffer at
// this point. This makes the first subscription to a channel
// consistent with subsquent ones in terms of late join semantics.
ChannelRecord c{
0, _md_consumer.get_channel(user_symbol), {}, user_symbol};
_trades_buffer.Clear();
c.cursor = c.channel.read_trades(_trades_buffer, c.cursor);
c.securities.push_back(sk);
_channels.push_back(c);
}
void on_status_sub(SecurityKey sk) {
auto k = sk.value();
for (auto &h : _status_subs) {
if (h.handle.key() == k) {
// reset status so it get resent next on_poll
h.status = co2::TRADINGSTATUS_UNKNOWN;
return;
}
}
auto handle = _md_consumer.get_managed_handle(k);
if (handle->is_initialized()) {
_status_subs.push_back(
StatusRecord{*handle, co2::TRADINGSTATUS_UNKNOWN});
} else {
LOG(OrderMan, Warn, "No market data for {}. Pending handle added.", k);
_pending_handles.push_back({handle, MDRequestType::STATUS});
}
}
void on_status_sub(SecurityKey sk);
struct HandleRecord {
utils::TimeStamp last;
@@ -361,7 +70,7 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
struct ChannelRecord {
int64_t cursor;
co2::MDConsumerChannel channel;
md::MDConsumerChannel channel;
boost::container::small_vector<TickerUpdateEvent, 5> securities;
md::UserSymbol user_symbol;
};
@@ -4,7 +4,8 @@
namespace bts::orderman {
const std::string &getm(const NVMap &map, char const *key_name) {
const std::string &getm(
const std::map<std::string, std::string> &map, char const *key_name) {
auto it = map.find(key_name);
bts_assert_msg(
it != map.end(), "Could not find key_name" + std::string(key_name));
@@ -59,4 +60,4 @@ std::ostream &operator<<(
return out;
}
} // namespace bts::orderman
} // namespace bts::orderman
@@ -1,14 +1,8 @@
#pragma once
#include "ccapi_cpp/ccapi_message.h"
#include "ccapi_cpp/ccapi_session.h"
#include "lib/api/services/OrderService.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/utils/PBUtils.h"
#include "lib/utils/bts_assert.h"
#include <ccapi_cpp/ccapi_element.h>
#include <ccapi_cpp/ccapi_event.h>
#include <ccapi_cpp/ccapi_message.h>
@@ -17,14 +11,11 @@
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <variant>
using namespace std;
namespace bts::orderman {
using NVMap = std::map<std::string, std::string>;
const std::string &getm(const NVMap &map, char const *key_name);
const std::string &getm(
const std::map<std::string, std::string> &map, char const *key_name);
bts::api::orderman::OrderStatus convert_ccapi_status(const std::string &status);
// 1.
@@ -66,7 +57,9 @@ struct CCAPICreateOrderMsg {
double cumm_filled_qty;
bts::api::orderman::OrderStatus status;
std::string corr_id;
CCAPICreateOrderMsg(const NVMap &map, const std::string &corr_id)
CCAPICreateOrderMsg(
const std::map<std::string, std::string> &map,
const std::string &corr_id)
: order_id(getm(map, CCAPI_EM_ORDER_ID)),
instrument(getm(map, CCAPI_INSTRUMENT)),
side(
@@ -159,7 +152,7 @@ struct CCAPIOrderUpdateMsg {
double cumm_filled_price;
double cumm_filled_qty;
api::orderman::OrderStatus status;
CCAPIOrderUpdateMsg(const NVMap &map)
CCAPIOrderUpdateMsg(const std::map<std::string, std::string> &map)
: order_id(getm(map, CCAPI_EM_ORDER_ID)),
side(
getm(map, CCAPI_EM_ORDER_SIDE) == CCAPI_EM_ORDER_SIDE_BUY
@@ -211,7 +204,7 @@ struct CCAPITradeMsg {
double last_exec_price;
double last_exec_qty;
std::string trade_id;
CCAPITradeMsg(const NVMap &map)
CCAPITradeMsg(const std::map<std::string, std::string> &map)
: order_id(getm(map, CCAPI_EM_ORDER_ID)),
side(
getm(map, CCAPI_EM_ORDER_SIDE) == CCAPI_EM_ORDER_SIDE_BUY
@@ -261,4 +254,4 @@ void to_json(nlohmann::json &j, const DeribitOpenPosition &dop);
std::ostream &operator<<(
std::ostream &out, const DeribitOpenPosition &dob) noexcept;
} // namespace bts::orderman
} // namespace bts::orderman
@@ -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