Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cc1b38358 | ||
|
|
0f3169ef05 | ||
|
|
f72753cd77 | ||
|
|
ea180d8ebc | ||
|
|
9937fc7cd2 | ||
|
|
f3f53d05c8 | ||
|
|
b49b7f01d5 | ||
|
|
a699865342 | ||
|
|
1b5f58c572 | ||
|
|
bae0b797e9 | ||
|
|
79a69e29a0 | ||
|
|
38bf38c62d | ||
|
|
4e08a43d0c | ||
|
|
4f912f99bd | ||
|
|
ae7e1761a7 | ||
|
|
4891eea5ba | ||
|
|
bc36db0480 | ||
|
|
cdaba74c06 | ||
|
|
b045307f30 | ||
|
|
60a7b504db | ||
|
|
0f62f14a1d | ||
|
|
87030dbb88 | ||
|
|
e209ec59e4 | ||
|
|
92dbedf28f | ||
|
|
7993f75fb1 | ||
|
|
1cddd2bcf3 | ||
|
|
9f7e5846b8 | ||
|
|
f7b3aac11a | ||
|
|
ec32101cef | ||
|
|
4e0cf68fa3 | ||
|
|
4e9a7d576e | ||
|
|
5e056dfff4 | ||
|
|
4029ccf8e6 |
No files matched your search
+1
-1
Submodule externals updated: 433d413283...83046b27ef.
@@ -1,3 +1,21 @@
|
||||
1.1.20
|
||||
- Update CME market data handler for price precision extension.
|
||||
https://www.cmegroup.com/confluence/display/EPICSANDBOX/Price+Precision+Extension
|
||||
|
||||
1.1.8
|
||||
- Enabled wild cards in risk management UI
|
||||
- Fixed spreader order updates
|
||||
|
||||
1.1.7
|
||||
- Fixed spread leg price fill
|
||||
- Fixed HO cash per point value
|
||||
|
||||
1.1.6
|
||||
- Fixed occasional crash when filtering fills
|
||||
- Fixed several cash per point multipliers
|
||||
- Colored time of sales row with aggressor side
|
||||
- Iceberg orders now cancel (rathern than error) when a child is canceled
|
||||
|
||||
1.1.0
|
||||
- Added per user self match protection, location and self match protection FIX tag settings
|
||||
- Implemented a new "Trigger Order" algo
|
||||
|
||||
@@ -6,11 +6,14 @@ using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesVM : SecurityGrid<TimeAndSalesVM.Row, TradeInfo> {
|
||||
@@ -26,23 +29,32 @@ namespace mbts.spark {
|
||||
security_filter.load_state(filters.security);
|
||||
price_filter.load_state(filters.price);
|
||||
size_filter.load_state(filters.size);
|
||||
aggressor_filter.load_state(filters.aggressor_sides);
|
||||
trade_key_filter.load_state(filters.trade_key);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
// Columns
|
||||
add_column(TimeAndSalesView.Columns.Security, "Security", DIYGrid.DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.Time, "Time", DIYGrid.DefaultColumnWidths.Time, time_filter, DataGridFilterKnownTemplates.TimeFilter);
|
||||
add_column(TimeAndSalesView.Columns.Price, "Price", DIYGrid.DefaultColumnWidths.Price, price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_column(TimeAndSalesView.Columns.Size, "Size", DIYGrid.DefaultColumnWidths.Size, size_filter, DataGridFilterKnownTemplates.UIntMinMax);
|
||||
add_column(TimeAndSalesView.Columns.TradeKey, "Trade Key", DIYGrid.DefaultColumnWidths.TradeKey, trade_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.SecurityKey, "Security Key", DIYGrid.DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
void add_column(TimeAndSalesView.Columns col_key, string header, double width, IDataGridColumnFilter filter, string filter_template) =>
|
||||
columns.Add(new DIYGrid.RenderTextColumn<Row>(r => r[col_key], filter, filter_template) { Header = header, Width = width });
|
||||
//
|
||||
add_column(TimeAndSalesView.Columns.Security, DefaultColumnHeaders.Security, DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.Time, DefaultColumnHeaders.Time, DefaultColumnWidths.Time, time_filter, DataGridFilterKnownTemplates.TimeFilter);
|
||||
add_column(TimeAndSalesView.Columns.Price, DefaultColumnHeaders.Price, DefaultColumnWidths.Price, price_filter, DataGridFilterKnownTemplates.DoubleMinMax, System.Windows.HorizontalAlignment.Right);
|
||||
add_column(TimeAndSalesView.Columns.Size, DefaultColumnHeaders.Size, DefaultColumnWidths.Size, size_filter, DataGridFilterKnownTemplates.UIntMinMax, System.Windows.HorizontalAlignment.Right);
|
||||
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DIYGrid.DefaultColumnWidths.Aggressor, aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
|
||||
add_column(TimeAndSalesView.Columns.TradeKey, DefaultColumnHeaders.TradeKey, DIYGrid.DefaultColumnWidths.TradeKey, trade_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DIYGrid.DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
void add_column(TimeAndSalesView.Columns col_key, string header, double width, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new TimeAndSalesColumn(
|
||||
r => r[col_key]
|
||||
, (r) => _get_aggressor_brush(r)
|
||||
, filter
|
||||
, filter_template
|
||||
)
|
||||
{ Header = header, Width = width, horizontal_alignment = h_alignment }
|
||||
);
|
||||
|
||||
_resources.AddRange(
|
||||
show_ladder,
|
||||
DataGridFilterHelper.hook_filters(
|
||||
rows_view, time_filter, security_filter, price_filter, size_filter, trade_key_filter, security_key_filter
|
||||
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, trade_key_filter, security_key_filter
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -50,11 +62,22 @@ namespace mbts.spark {
|
||||
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
|
||||
public DelegateCommandParamObserver<Row> show_ladder { get; }
|
||||
|
||||
private Brush _get_aggressor_brush(Row r) {
|
||||
BTSColor c;
|
||||
Brush b;
|
||||
if (r.aggressor_side == AggressorSide.Buy) c = BTSColors.MarketData.bid;
|
||||
else if (r.aggressor_side == AggressorSide.Sell) c = BTSColors.MarketData.ask;
|
||||
else c = Brushes.Transparent;
|
||||
(b = new SolidColorBrush(c) { Opacity = 0.33 }).Freeze();
|
||||
return b;
|
||||
}
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> time_filter { get; } = new TimeFilterVM<Row>(r => r.time, nameof(Row.time));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<Row>(r => r.price, nameof(Row.price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> size_filter { get; } = new UIntMinMax<Row>(r => r.size, nameof(Row.size));
|
||||
public IDataGridColumnPersistentFilter<Row, AggressorSide[]> aggressor_filter { get; } = DiscreteValuesFilterVM.Create<Row, AggressorSide>(EnumHelper.get_values<AggressorSide>().ToArray(), r => r.aggressor_side, nameof(Row.aggressor_side));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
public IDataGridColumnPersistentFilter<Row, string> trade_key_filter { get; } = new RegexFilterVM<Row>(r => r.trade_key.ToString(), nameof(Row.trade_key));
|
||||
|
||||
@@ -65,7 +88,8 @@ namespace mbts.spark {
|
||||
price = price_filter.get_state(),
|
||||
size = size_filter.get_state(),
|
||||
trade_key = trade_key_filter.get_state(),
|
||||
security_key = security_key_filter.get_state()
|
||||
security_key = security_key_filter.get_state(),
|
||||
aggressor_sides = aggressor_filter.get_state()
|
||||
};
|
||||
#endregion
|
||||
|
||||
@@ -90,6 +114,7 @@ namespace mbts.spark {
|
||||
_time_text,
|
||||
_price_text,
|
||||
_size_text,
|
||||
_aggressor_text,
|
||||
_trade_key_text,
|
||||
_security_key_text;
|
||||
|
||||
@@ -98,11 +123,13 @@ namespace mbts.spark {
|
||||
time = trade.Time.lldatetime();
|
||||
price = trade.Mantissa * Math.Pow(10, trade.Exponent);
|
||||
size = trade.Size;
|
||||
aggressor_side = trade.AggressorSide;
|
||||
show_ladder = new DelegateCommand(() => parent._show_ladder_async(this).ignore());
|
||||
_security_text = security.ToString();
|
||||
_time_text = time.ToString("M/d h:mm:ss tt");
|
||||
_price_text = security.ContractSpec.format(price);
|
||||
_size_text = size.ToString();
|
||||
_aggressor_text = aggressor_side.ToString();
|
||||
_trade_key_text = _trade_key.ToString();
|
||||
_security_key_text = security_key.ToString();
|
||||
}
|
||||
@@ -113,6 +140,7 @@ namespace mbts.spark {
|
||||
public DateTime time { get; }
|
||||
public double price { get; }
|
||||
public uint size { get; }
|
||||
public AggressorSide aggressor_side { get; }
|
||||
public ICommand show_ladder { get; }
|
||||
|
||||
public string this[TimeAndSalesView.Columns col] {
|
||||
@@ -122,6 +150,7 @@ namespace mbts.spark {
|
||||
case TimeAndSalesView.Columns.Time: return _time_text;
|
||||
case TimeAndSalesView.Columns.Price: return _price_text;
|
||||
case TimeAndSalesView.Columns.Size: return _size_text;
|
||||
case TimeAndSalesView.Columns.Aggressor: return _aggressor_text;
|
||||
case TimeAndSalesView.Columns.TradeKey: return _trade_key_text;
|
||||
case TimeAndSalesView.Columns.SecurityKey: return _security_key_text;
|
||||
default: return null;
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for TimeAndSalesView.xaml
|
||||
/// </summary>
|
||||
partial class TimeAndSalesView : UserControl {
|
||||
public enum Columns { Time, Security, Price, Size, OrderType, TradeKey, SecurityKey }
|
||||
public enum Columns { Time, Security, Price, Size, OrderType, TradeKey, SecurityKey, Aggressor }
|
||||
|
||||
public TimeAndSalesView() =>
|
||||
InitializeComponent();
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISlowWindow {
|
||||
@@ -36,7 +37,16 @@ namespace mbts.spark {
|
||||
public override string window_type => _window_type;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override UIElement Content => _view;
|
||||
public override double MinWidth => 444 + SPARK_MENU_COLLAPSED_WIDTH + LAUNCHER_BUTTON_BORDER_WIDTH;
|
||||
public override double MinWidth => DefaultColumnWidths.Security
|
||||
+ DefaultColumnWidths.Time
|
||||
+ DefaultColumnWidths.Price
|
||||
+ DefaultColumnWidths.Size
|
||||
+ DefaultColumnWidths.Aggressor
|
||||
+ DefaultColumnWidths.TradeKey
|
||||
+ DefaultColumnWidths.SecurityKey
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH;
|
||||
|
||||
public override StartupData SaveState => _state = new StartupData(_state.security_criteria, _vm.value.rows_view.GetSortingCriteria(), _vm.value.get_filter_states());
|
||||
|
||||
public override void Dispose() =>
|
||||
|
||||
@@ -70,6 +70,8 @@ namespace bts.shell {
|
||||
public string trade_key;
|
||||
[ProtoMember(6)]
|
||||
public string security_key;
|
||||
[ProtoMember(7)]
|
||||
public co2.AggressorSide[] aggressor_sides;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,8 +84,11 @@ namespace bts.shell {
|
||||
public DoubleMinMaxState total_pnl;
|
||||
[ProtoMember(12)]
|
||||
public DoubleMinMaxState pnl_price;
|
||||
[ProtoMember(13)]
|
||||
public Fill.Types.FillKind[] fill_kind;
|
||||
|
||||
// [ProtoMember(13)]
|
||||
// ProtoMember 13 cannot be used further
|
||||
[ProtoMember(14)]
|
||||
public SecurityType[] security_types;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ namespace mbts.ui.controls {
|
||||
Time = 100,
|
||||
TimeInForce = 47,
|
||||
TradeKey = 76,
|
||||
User = 53;
|
||||
User = 53,
|
||||
Aggressor = 80;
|
||||
}
|
||||
|
||||
public static class DefaultColumnHeaders {
|
||||
@@ -40,7 +41,9 @@ namespace mbts.ui.controls {
|
||||
Size = "Qty",
|
||||
TimeInForce = "TIF",
|
||||
TradeKey = "Trade Key",
|
||||
User = "User";
|
||||
User = "User",
|
||||
Time = "Time",
|
||||
Aggressor = "Aggressor";
|
||||
}
|
||||
|
||||
internal interface IKeyedColumn<TColKey> {
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
public partial class DIYGrid {
|
||||
internal class TimeAndSalesColumn : RenderTextColumn<TimeAndSalesVM.Row> {
|
||||
|
||||
readonly Func<TimeAndSalesVM.Row, string> _get_text;
|
||||
readonly Func<TimeAndSalesVM.Row, Brush> _get_brush;
|
||||
|
||||
public TimeAndSalesColumn(Func<TimeAndSalesVM.Row, string> get_text, Func<TimeAndSalesVM.Row, Brush> get_brush, IDataGridColumnFilter filter = null, string filter_template = null, GlyphContext glyph_context = null) : base(get_text, filter, filter_template, glyph_context) {
|
||||
_get_text = get_text;
|
||||
_get_brush = get_brush;
|
||||
}
|
||||
|
||||
public override void draw(TimeAndSalesVM.Row row, DrawingContext dc, ColumnGeometry position, double row_height) {
|
||||
CellRender.draw_text(dc
|
||||
, glyph_context
|
||||
, font_size
|
||||
, _get_text(row)
|
||||
, position
|
||||
, row_height
|
||||
, foreground
|
||||
, _get_brush(row)
|
||||
, horizontal_alignment
|
||||
, vertical_alignment
|
||||
, padding);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,17 +190,21 @@ namespace mbts.ui.controls
|
||||
}
|
||||
}
|
||||
|
||||
void item_Unselected(object sender, RoutedEventArgs e) {
|
||||
if (_selection_changing) return;
|
||||
var item = ((ListBoxItem)sender).DataContext;
|
||||
var selected_items = Selection;
|
||||
if (selected_items.Contains(item)) {
|
||||
using (_selection_change()) {
|
||||
selected_items.Remove(item);
|
||||
_update_is_all_selected();
|
||||
}
|
||||
}
|
||||
}
|
||||
void item_Unselected(object sender, RoutedEventArgs e) {
|
||||
if (!_selection_changing && sender is ListBoxItem container) {
|
||||
// the [ListBoxItem] sender may be "Disconnected" https://stackoverflow.com/questions/14282894/wpf-listbox-virtualization-creates-disconnecteditems/14358688
|
||||
var item = ItemContainerGenerator.ItemFromContainer(container);
|
||||
if (item != DependencyProperty.UnsetValue) {
|
||||
var selected_items = Selection;
|
||||
if (selected_items.Contains(item)) {
|
||||
using (_selection_change()) {
|
||||
selected_items.Remove(item);
|
||||
_update_is_all_selected();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void item_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {
|
||||
using (_selection_change()) ((ListBoxItem)sender).IsSelected = Selection.Contains(e.NewValue);
|
||||
@@ -368,4 +372,4 @@ namespace mbts.ui.controls
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,7 +376,7 @@ namespace mbts.spark
|
||||
return x is SecurityImmutable && y is SecurityImmutable ? Compare((SecurityImmutable)x, (SecurityImmutable)y) : -1;
|
||||
}
|
||||
public int Compare(SecurityImmutable x, SecurityImmutable y) {
|
||||
return x.EarliestExpiration().CompareTo(y.EarliestExpiration());
|
||||
return SecurityHelper.Comparer.Compare(x, y);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace mbts.spark {
|
||||
total_net_position_filter.load_state(fs.total_net_pos);
|
||||
total_pnl_filter.load_state(fs.total_pnl);
|
||||
pnl_price_filter.load_state(fs.pnl_price);
|
||||
fill_kind_filter.load_state(fs.fill_kind);
|
||||
security_type_filter.load_state(fs.security_types);
|
||||
}
|
||||
// restore sort state or supply default value
|
||||
if (startup_data?.sort_criteria?.Any() == true) {
|
||||
@@ -92,7 +92,7 @@ namespace mbts.spark {
|
||||
pnl_price_filter,
|
||||
weighted_average_buy_filter,
|
||||
weighted_average_sell_filter,
|
||||
fill_kind_filter),
|
||||
security_type_filter),
|
||||
WorkLoop.Start("PAndL", _UPDATE_PERIOD, _calculate_pnl)
|
||||
);
|
||||
}
|
||||
@@ -197,7 +197,7 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> pnl_price_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.pnl_price, nameof(PnLSummaryRow.pnl_price));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> weighted_average_buy_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.weighted_average_buy, nameof(PnLSummaryRow.weighted_average_buy));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, DoubleMinMaxState> weighted_average_sell_filter { get; } = new DoubleMinMax<PnLSummaryRow>(r => r.weighted_average_sell, nameof(PnLSummaryRow.weighted_average_sell));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, FillKind[]> fill_kind_filter { get; } = DiscreteValuesFilterVM.Create<PnLSummaryRow, FillKind>(EnumHelper.get_values<FillKind>().ToArray(), r => r.fill_kind, nameof(PnLSummaryRow.fill_kind));
|
||||
public IDataGridColumnPersistentFilter<PnLSummaryRow, SecurityType[]> security_type_filter { get; } = DiscreteValuesFilterVM.Create<PnLSummaryRow, SecurityType>(EnumHelper.get_values_except<SecurityType>(SecurityType.Option, SecurityType.Stock, SecurityType.Synthetic).ToArray(), r => r.security_type, nameof(PnLSummaryRow.security_type));
|
||||
|
||||
public FilterStates get_filters_states() => new FilterStates {
|
||||
account_regex = account_filter.get_state(),
|
||||
@@ -212,7 +212,7 @@ namespace mbts.spark {
|
||||
total_net_pos = total_net_position_filter.get_state(),
|
||||
total_pnl = total_pnl_filter.get_state(),
|
||||
pnl_price = pnl_price_filter.get_state(),
|
||||
fill_kind = fill_kind_filter.get_state()
|
||||
security_types = security_type_filter.get_state()
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
</Style>
|
||||
<convert:NumericSignBrushConverter x:Key="PnLBackgroundColorConverter" Opacity="0.33"/>
|
||||
<convert:DoubleTextConverter x:Key="DoubleWithNaNHiddenConverter" Format="F2" />
|
||||
<convert:DoubleTextConverter x:Key="DoubleWithNaNHiddenConverter" Format="N0" />
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
@@ -64,12 +64,12 @@
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource RegexTemplate}"
|
||||
/>
|
||||
<DataGridTextColumn x:Name="fill_kind" Header="Fill Kind" Binding="{Binding fill_kind}" IsReadOnly="true"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.fill_kind_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.fill_kind_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.fill_kind_filter.open, Source={uc:DirectReference _grid}}"
|
||||
<DataGridTextColumn x:Name="security_type" Header="Security Type" Binding="{Binding security_type}" IsReadOnly="true"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.security_type_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.security_type_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_type_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DiscreteValuesTemplate}"
|
||||
/>
|
||||
/>
|
||||
<DataGridTextColumn x:Name="start_net_pos" Header="Start Net Pos" Binding="{Binding start_net_position}" IsReadOnly="true"
|
||||
ElementStyle="{StaticResource RightAlignedColumn}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.start_position_filter, Source={uc:DirectReference _grid}}"
|
||||
@@ -78,16 +78,11 @@
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}"
|
||||
/>
|
||||
<DataGridTextColumn x:Name="start_pnl" Header="Start PnL" IsReadOnly="true" SortMemberPath="start_pnl"
|
||||
Binding="{Binding start_pnl, Converter={StaticResource DoubleWithNaNHiddenConverter}}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.start_pnl_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.start_pnl_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.start_pnl_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}">
|
||||
<DataGridTextColumn.Binding>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="start_pnl"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</DataGridTextColumn.Binding>
|
||||
<DataGridTextColumn.ElementStyle>
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
@@ -110,16 +105,11 @@
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}"
|
||||
/>
|
||||
<DataGridTextColumn x:Name="day_pnl" Header="Day PnL" IsReadOnly="true" SortMemberPath="day_pnl"
|
||||
Binding="{Binding day_pnl, Converter={StaticResource DoubleWithNaNHiddenConverter}}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.day_pnl_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_pnl_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_pnl_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}">
|
||||
<DataGridTextColumn.Binding>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="day_pnl"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</DataGridTextColumn.Binding>
|
||||
<DataGridTextColumn.ElementStyle>
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
@@ -135,16 +125,11 @@
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}"
|
||||
/>
|
||||
<DataGridTextColumn x:Name="total_pnl" Header="Total PnL" IsReadOnly="true" SortMemberPath="total_pnl"
|
||||
Binding="{Binding total_pnl, Converter={StaticResource DoubleWithNaNHiddenConverter}}"
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.total_pnl_filter, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_pnl_filter.active, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.total_pnl_filter.open, Source={uc:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}">
|
||||
<DataGridTextColumn.Binding>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="total_pnl"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</DataGridTextColumn.Binding>
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource DoubleMinMaxTemplate}">
|
||||
<DataGridTextColumn.ElementStyle>
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
|
||||
@@ -86,12 +86,16 @@ namespace mbts.spark {
|
||||
// Note that this isn't the acutal start of the trading day
|
||||
// but it's close enough for pnl purposes.
|
||||
// In reality, instruments have different schedules
|
||||
_start_of_trading_day_utc = DateTime.Now.Date.Add(start_of_day_ts).AddDays(-1).ToUniversalTime();
|
||||
var _start_of_trading_day_local = DateTime.Now.Date.Add(start_of_day_ts);
|
||||
if (_start_of_trading_day_local >= DateTime.Now) {
|
||||
_start_of_trading_day_local = _start_of_trading_day_local.AddDays(-1);
|
||||
}
|
||||
_start_of_trading_day_utc = _start_of_trading_day_local.ToUniversalTime();
|
||||
}
|
||||
|
||||
public IEnumerable<PnLSummaryRow> get_summary_position() {
|
||||
return (from pnl_item in _data
|
||||
group pnl_item by (pnl_item.account, pnl_item.exchange, pnl_item.symbol, pnl_item.security, pnl_item.sec_md, pnl_item.fill_kind) into grouping
|
||||
group pnl_item by (pnl_item.account, pnl_item.exchange, pnl_item.symbol, pnl_item.security, pnl_item.sec_md, pnl_item.security_type) into grouping
|
||||
/* let start_of_day = _get_start_of_day(grouping.Key.symbol) */
|
||||
select new PnLSummaryRow(_calc_type, _start_of_trading_day_utc, grouping.Key,
|
||||
grouping.Where(y => y.fill.Timestamp.ToDateTime() >= _start_of_trading_day_utc).Sum(y => (uint)y.buy_quantity),
|
||||
@@ -191,7 +195,7 @@ namespace mbts.spark {
|
||||
if (pnl_request.HasFlag(pnl_calc_type.last)) { driver_price = last.price; return true; }
|
||||
// note that bid_or_ask is not considered here since this method only
|
||||
// supports returning a single driver price; see update_pnl
|
||||
if (pnl_request.HasFlag(pnl_calc_type.midpoint)) { driver_price = TickUtils.mid_tick(sec_md.security.ContractSpec, bid.price, ask.price); return true; }
|
||||
if (pnl_request.HasFlag(pnl_calc_type.midpoint)) { driver_price = (bid.price + ask.price) / 2.0; return true; }
|
||||
if (pnl_request.HasFlag(pnl_calc_type.open)) { driver_price = sec_md.md.open_price; return true; }
|
||||
if (pnl_request.HasFlag(pnl_calc_type.close)) { driver_price = sec_md.md.close_price; return true; }
|
||||
if (pnl_request.HasFlag(pnl_calc_type.settle)) { driver_price = sec_md.md.settlement_price; return true; }
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace mbts.spark {
|
||||
public SecurityImmutable? security => sec_md?.security;
|
||||
public double buy_quantity => (fill.Side == Side.Buy) ? fill.LastShares : 0;
|
||||
public double sell_quantity => (fill.Side == Side.Sell) ? fill.LastShares : 0;
|
||||
public Fill.Types.FillKind fill_kind => fill.FillKind;
|
||||
public SecurityType? security_type => security?.security_type();
|
||||
|
||||
public override string ToString() {
|
||||
return $"Fill: ( {account} {exchange} {security.ToString()} buy: {buy_quantity} sell: {sell_quantity} ) Security: {security.ToString()}";
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace mbts.spark {
|
||||
|
||||
internal class PnLSummaryRow : ViewModelBase, IEquatable<PnLSummaryRow> {
|
||||
DateTime _start_of_day;
|
||||
(string account, Venue? exchange, string symbol, SecurityImmutable? security, SecurityMD? sec_md, Fill.Types.FillKind fill_kind) key;
|
||||
(string account, Venue? exchange, string symbol, SecurityImmutable? security, SecurityMD? sec_md, SecurityType? sec_type) key;
|
||||
int _start_net_position, _total_net_position;
|
||||
uint _day_long_quantity, _day_short_quantity;
|
||||
double _total_pnl, _sod_pnl, _start_pnl, _pnl_price, _weighted_average_buy, _weighted_average_sell;
|
||||
@@ -24,7 +24,7 @@ namespace mbts.spark {
|
||||
|
||||
public PnLSummaryRow(pnl_calc_type calc_type,
|
||||
DateTime start_of_day,
|
||||
(string, Venue?, string, SecurityImmutable?, SecurityMD?, Fill.Types.FillKind) key,
|
||||
(string, Venue?, string, SecurityImmutable?, SecurityMD?, SecurityType?) key,
|
||||
uint day_buy_quant, uint day_sell_quant, int net_pos, int sod_pos, double wavg_buy, double wavg_sell, IEnumerable<FillImmutable> fills) {
|
||||
this._start_of_day = start_of_day;
|
||||
this.key = key;
|
||||
@@ -41,19 +41,21 @@ namespace mbts.spark {
|
||||
update_pnl(calc_type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up the cash per point (cpp) for the given SecurityImmutable.
|
||||
/// If the SecurityImmutable is a cross-product exchange-traded spread,
|
||||
/// such as a crack spread, where the legs have differing cpps, then
|
||||
/// we'll return Double.Nan;
|
||||
/// </summary>
|
||||
/// <param name="s">The SecurityImmutable for which we will look up a cpp</param>
|
||||
/// <returns>The found cpp, else Double.NaN</returns>
|
||||
private double _get_cpp_for_security(SecurityImmutable s) {
|
||||
if (s.security_type() == SecurityType.Spread) {
|
||||
var distinct_cpps = s.Spread.Legs
|
||||
.Select(l => get_cpp_for_symbol(l.Security))
|
||||
.Distinct();
|
||||
bool all_legs_have_same_cpp = distinct_cpps.Count() < 2;
|
||||
if (all_legs_have_same_cpp) {
|
||||
return distinct_cpps.First();
|
||||
} else {
|
||||
Logger.error(bts.log.Subsystem.Ui,
|
||||
$"Security {s} contains legs with the following cash multipliers: { string.Join(",", distinct_cpps.Select(d => d.ToString())) } ");
|
||||
return Double.NaN;
|
||||
}
|
||||
return all_legs_have_same_cpp ? distinct_cpps.First() : Double.NaN;
|
||||
} else {
|
||||
return get_cpp_for_symbol(s);
|
||||
}
|
||||
@@ -62,8 +64,6 @@ namespace mbts.spark {
|
||||
double get_cpp_for_symbol(SecurityImmutable si) {
|
||||
if (cpp_dict.TryGetValue(si.Symbol, out double cpp_out)) {
|
||||
return cpp_out;
|
||||
} else {
|
||||
Logger.error(bts.log.Subsystem.Ui, $"No cash multiplier found for Security {si}, Symbol {si.Symbol}");
|
||||
}
|
||||
return double.NaN;
|
||||
}
|
||||
@@ -170,14 +170,13 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public SecurityImmutable? security => key.security;
|
||||
public SecurityType security_type => key.sec_type.GetValueOrDefault();
|
||||
|
||||
public string security_text {
|
||||
get => security.HasValue ? security.Value.ToString() : "Unknown Security";
|
||||
set => ignore();
|
||||
}
|
||||
|
||||
public Fill.Types.FillKind fill_kind => key.fill_kind;
|
||||
|
||||
|
||||
public int start_net_position {
|
||||
get => _start_net_position;
|
||||
|
||||
@@ -124,8 +124,8 @@ namespace mbts.co2
|
||||
public double session_high { get { return _cold->session_high;} }
|
||||
public double session_low { get { return _cold->session_low; } }
|
||||
public double open_interest { get { return _cold->open_interest; } }
|
||||
public double session_high_bid { get { return _cold->session_high_bid; } }
|
||||
public double session_low_offer { get { return _cold->session_low_offer; } }
|
||||
// public double session_high_bid { get { return _cold->session_high_bid; } }
|
||||
// public double session_low_offer { get { return _cold->session_low_offer; } }
|
||||
public double fixing_price { get { return _cold->fixing_price; } }
|
||||
|
||||
//
|
||||
|
||||
+1399
-5505
File diff suppressed because it is too large.
Load diff
@@ -28,8 +28,10 @@ double dump(TimeStamp time) {
|
||||
string dump(PriceSize ps) {
|
||||
stringstream res;
|
||||
res << "{";
|
||||
res << "'price': " << ps.price() << ", ";
|
||||
res << "'size': " << ps.size();
|
||||
res << "'price': " << std::setprecision(11) << ps.price() << ", ";
|
||||
res << "'size': " << ps.size() << " ";
|
||||
// res << "'mant': " << ps.mantissa() << ", ";
|
||||
// res << "'exp': " << (int)ps.exponent() << " ";
|
||||
res << "}";
|
||||
return res.str();
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ void MDProducerHandle::set_status(CoconutSecurityStatus status) {
|
||||
}
|
||||
}
|
||||
|
||||
void MDProducerHandle::add_trade(TimeStamp time, const PriceSize price, LLDateTime tradetime, uint32_t volume) {
|
||||
void MDProducerHandle::add_trade(TimeStamp time, const PriceSize price, LLDateTime tradetime, co2::AggressorSide agside, uint32_t volume) {
|
||||
cdata().last_trade.store(price);
|
||||
cdata().last_trade_time = tradetime;
|
||||
cdata().total_volume = volume;
|
||||
@@ -127,6 +127,7 @@ void MDProducerHandle::add_trade(TimeStamp time, const PriceSize price, LLDateTi
|
||||
trade.set_mantissa(price.mantissa());
|
||||
trade.set_size(price.size());
|
||||
trade.set_time(tradetime);
|
||||
trade.set_aggressor_side(agside);
|
||||
if (volume > 0) trade.set_total_volume(volume);
|
||||
trade.set_key(key());
|
||||
_channel.add_trade(trade);
|
||||
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
void set_status(co2::CoconutSecurityStatus status); // hot
|
||||
|
||||
// Updates trade log and last trade
|
||||
void add_trade(TimeStamp time, const PriceSize pricesize, datetime::LLDateTime date, uint32_t total_volume=0); // cold
|
||||
void add_trade(TimeStamp time, const PriceSize pricesize, datetime::LLDateTime date, co2::AggressorSide agside, uint32_t total_volume=0); // cold
|
||||
// Update last_trade without updating trade log
|
||||
void set_last_trade(TimeStamp time, const PriceSize ps, datetime::LLDateTime date, uint32_t total_volume);
|
||||
void add_rfq(TimeStamp time, const co2::QuoteRequestMessage& rfq); // cold
|
||||
|
||||
@@ -119,6 +119,12 @@ message MiscMessage {
|
||||
double session_low_offer = 12;
|
||||
double fixing_price = 13;
|
||||
}
|
||||
|
||||
enum AggressorSide {
|
||||
AGGRESSOR_SIDE_NONE = 0;
|
||||
AGGRESSOR_SIDE_BUY = 1;
|
||||
AGGRESSOR_SIDE_SELL = 2;
|
||||
}
|
||||
|
||||
message TradeMessage {
|
||||
uint32 key = 1;
|
||||
@@ -130,6 +136,7 @@ message TradeMessage {
|
||||
uint32 size = 6;
|
||||
uint32 total_volume = 7;
|
||||
bool gap = 8;
|
||||
AggressorSide aggressor_side = 9;
|
||||
}
|
||||
message QuoteRequestMessage {
|
||||
enum Side {
|
||||
|
||||
@@ -24,7 +24,7 @@ target_link_libraries( sparkd
|
||||
tbb
|
||||
double-conversion
|
||||
pugixml
|
||||
onixs-cmemdh-all-4.5.2
|
||||
onixs-cmemdh-all-4.8.0
|
||||
quickfix
|
||||
benchmark
|
||||
lttng-ust
|
||||
|
||||
@@ -474,7 +474,8 @@ void ExchangeSimApplication::update_md(std::string const &sec_desc) {
|
||||
utils::TimeStamp::now(),
|
||||
md::PriceSize::from_double<7>(t.price.value() * price_factor,
|
||||
t.quantity.value()),
|
||||
datetime::ll_now());
|
||||
datetime::ll_now(),
|
||||
co2::AGGRESSOR_SIDE_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ struct ApiGatewayImpl {
|
||||
// buffer = [size][ResponseHeader][Payload].
|
||||
GatewayResponse response;
|
||||
response.set_call_result(buffer, length);
|
||||
LOG(Api,Info, "Gateway Result: {}", api::toJSON(response));
|
||||
LOG(Api,Diag, "Gateway Result: {}", api::toJSON(response));
|
||||
// in response send to client
|
||||
const Responder* r = static_cast<const Responder*>(extra);
|
||||
if (r != nullptr) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <vector>
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
#include "lib/md/protos/co2.pb.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace bts::co2;
|
||||
@@ -10,74 +11,6 @@ using namespace bts::co2;
|
||||
namespace bts {
|
||||
namespace md {
|
||||
|
||||
void decode_ticks(int vtt, double mintick, FixedSecurity& dest) {
|
||||
switch(vtt) {
|
||||
default:
|
||||
case 0:
|
||||
dest.low_tick = mintick;
|
||||
dest.high_tick = mintick;
|
||||
dest.price_break = 0;
|
||||
break;
|
||||
case 1:
|
||||
dest.price_break = 500;
|
||||
dest.low_tick = 5;
|
||||
dest.high_tick = 10;
|
||||
break;
|
||||
case 2:
|
||||
dest.price_break = 5;
|
||||
dest.low_tick = 0.5;
|
||||
dest.high_tick = 1;
|
||||
break;
|
||||
case 3:
|
||||
dest.price_break = 10;
|
||||
dest.low_tick = 1;
|
||||
dest.high_tick = 2;
|
||||
break;
|
||||
case 4:
|
||||
dest.price_break = 500;
|
||||
dest.low_tick = 5;
|
||||
dest.high_tick = 25;
|
||||
break;
|
||||
case 10:
|
||||
dest.price_break = 300;
|
||||
dest.low_tick = 5;
|
||||
dest.high_tick = 25;
|
||||
break;
|
||||
case 11:
|
||||
dest.price_break = 300;
|
||||
dest.low_tick = 5;
|
||||
dest.high_tick = 10;
|
||||
break;
|
||||
case 12:
|
||||
dest.price_break = 5;
|
||||
dest.low_tick = 0.25;
|
||||
dest.high_tick = 0.5;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int floorlog10(int64_t m) {
|
||||
bts_assert(m >= 0);
|
||||
if (m < 10) return 0;
|
||||
if (m < 100) return 1;
|
||||
if (m < 1000) return 2;
|
||||
if (m < 10000) return 3;
|
||||
if (m < 100000) return 4;
|
||||
if (m < 1000000) return 5;
|
||||
if (m < 10000000) return 6;
|
||||
if (m < 100000000) return 7;
|
||||
if (m < 1000000000) return 8;
|
||||
if (m < 10000000000) return 9;
|
||||
if (m < 100000000000) return 10;
|
||||
if (m < 1000000000000) return 11;
|
||||
if (m < 10000000000000) return 12;
|
||||
if (m < 100000000000000) return 13;
|
||||
if (m < 1000000000000000) return 14;
|
||||
if (m < 10000000000000000) return 15;
|
||||
if (m < 100000000000000000) return 16;
|
||||
if (m < 1000000000000000000) return 17;
|
||||
return 19;
|
||||
}
|
||||
|
||||
MDP3Listener::MDP3Listener(
|
||||
CMESecurityIndex& index,
|
||||
@@ -181,159 +114,6 @@ void MDP3Listener::onEndOfPacket(onixs::Handler&, const onixs::PacketArgs&) {
|
||||
//
|
||||
// Security discovery
|
||||
|
||||
template <class Price>
|
||||
double double_of_price(const Price& price, int8_t display_factor) {
|
||||
// order of multiplication is important because of floating point error
|
||||
return (price.mantissa() * pow10(price.exponent())) * pow10(display_factor);
|
||||
}
|
||||
|
||||
inline datetime::LLDateTime ll_of_timestamp(const onixs::Timestamp& ts) {
|
||||
return datetime::ll_from_parts(ts.year(), (int)ts.month(), ts.day(), ts.hour(), ts.minute(), ts.second(), ts.millisecond());
|
||||
}
|
||||
|
||||
datetime::Date date_of_timestamp(const onixs::Timestamp& ts) {
|
||||
return datetime::Date(ts.year(), (int)ts.month(), ts.day());
|
||||
}
|
||||
|
||||
template <class Definition>
|
||||
datetime::Date date_of_maturity(const Definition& def) {
|
||||
for (const auto& event : def.events()) {
|
||||
if (event.eventType() == onixs::EventType::LastEligibleTradeDate) {
|
||||
return date_of_timestamp(event.eventTime());
|
||||
}
|
||||
}
|
||||
return datetime::Date();
|
||||
}
|
||||
|
||||
|
||||
template <class Definition>
|
||||
void set_security_common(const Definition& def, FixedSecurity& fsec) {
|
||||
fsec.numeric_id = def.securityId();
|
||||
fsec.symbol(def.asset().items(), def.asset().size());
|
||||
fsec.cfi_code(def.cfiCode().items(), def.cfiCode().size());
|
||||
fsec.alt_symbol(def.securityGroup().items(), def.securityGroup().size());
|
||||
fsec.security_desc(def.symbol().items(), def.symbol().size());
|
||||
fsec.exchange = api::Venue::VENUE_GLBX;
|
||||
// tickRule not present in futures
|
||||
fsec.cme_market_segment_id = def.marketSegmentId();
|
||||
fsec.display_factor = def.displayFactor().exponent() + floorlog10(def.displayFactor().mantissa());
|
||||
|
||||
def.mainFraction(fsec.cme_main_fraction);
|
||||
def.subFraction(fsec.cme_sub_fraction);
|
||||
def.priceDisplayFormat(fsec.cme_price_display_format);
|
||||
}
|
||||
|
||||
void MDP3Listener::onDefinition(onixs::Handler&, const onixs::Security& osec, const onixs::InstrumentDefinitionFutureArgs& def) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onDefinition.future", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
const auto& sec = def.message();
|
||||
auto exchange_id = sec.securityId();
|
||||
// LOG(md, Info, "onDefinition future {} {}",
|
||||
// std::string(sec.asset().items(), sec.asset().size()),
|
||||
// std::string(sec.symbol().items(), sec.symbol().size()));
|
||||
|
||||
if (filter_security(exchange_id, sec) != ADD) return;
|
||||
bts_assert(exchange_id == osec.id());
|
||||
|
||||
FixedFutureSecurity fsec;
|
||||
|
||||
set_security_common(sec, fsec);
|
||||
fsec.price_break = 0;
|
||||
// low/high tick are stored non-premultiplied and multiplied in conversion to api::Secutity
|
||||
double min_tick = double_of_price(sec.minPriceIncrement(), 0);
|
||||
fsec.low_tick = fsec.high_tick = min_tick;
|
||||
onixs::PRICE min_tick_amount;
|
||||
sec.minPriceIncrementAmount(min_tick_amount);
|
||||
double min_tick_dbl = double_of_price(min_tick_amount, 0);
|
||||
fsec.cash_per_point = min_tick_dbl / min_tick / pow10(fsec.display_factor);
|
||||
auto expiration = date_of_maturity(sec);
|
||||
fsec.expiration_year = expiration.year;
|
||||
fsec.expiration_month = expiration.month;
|
||||
fsec.expiration_day = expiration.day;
|
||||
add_security(fsec);
|
||||
}
|
||||
void MDP3Listener::onDefinition(onixs::Handler&, const onixs::Security&, const onixs::InstrumentDefinitionOptionArgs& def) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onDefinition.option", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
const auto& sec = def.message();
|
||||
auto exchange_id = sec.securityId();
|
||||
// LOG(Info, MarketData) << "onDefinition option " << exchange_id;
|
||||
if (filter_security(exchange_id, sec) != ADD) return;
|
||||
|
||||
FixedOptionSecurity fsec;
|
||||
set_security_common(sec, fsec);
|
||||
auto expiration = date_of_maturity(sec);
|
||||
fsec.expiration_year = expiration.year;
|
||||
fsec.expiration_month = expiration.month;
|
||||
fsec.expiration_day = expiration.day;
|
||||
|
||||
onixs::PRICE strike_price;
|
||||
bts_assert(sec.strikePrice(strike_price));
|
||||
fsec.strike = double_of_price(strike_price, fsec.display_factor);
|
||||
|
||||
onixs::PRICE min_tick, min_tick_amount;
|
||||
sec.minPriceIncrement(min_tick);
|
||||
sec.minPriceIncrementAmount(min_tick_amount);
|
||||
|
||||
int8_t tick_rule;
|
||||
if (sec.tickRule(tick_rule)) {
|
||||
decode_ticks(tick_rule, double_of_price(min_tick, 0), fsec);
|
||||
}
|
||||
fsec.cash_per_point = double_of_price(min_tick_amount, 0) / fsec.low_tick / pow10(fsec.display_factor);
|
||||
|
||||
fsec.call_put = sec.putOrCall() == onixs::PutOrCall::Call ? api::PUTORCALL_CALL : api::PUTORCALL_PUT;
|
||||
|
||||
add_security(fsec);
|
||||
}
|
||||
|
||||
void MDP3Listener::onDefinition(onixs::Handler&, const onixs::Security&, const onixs::InstrumentDefinitionSpreadArgs& spread_args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onDefinition.spread", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
const auto& sec = spread_args.message();
|
||||
auto exchange_id = sec.securityId();
|
||||
// LOG(md, Info, "onDefinition spread {} {}",
|
||||
// std::string(sec.asset().items(), sec.asset().size()),
|
||||
// std::string(sec.symbol().items(), sec.symbol().size()));
|
||||
if (filter_security(exchange_id, sec) != ADD) return;
|
||||
|
||||
// Filter out any already expired spreads
|
||||
auto expiration = date_of_maturity(sec);
|
||||
if (expiration < datetime::today()) return;
|
||||
|
||||
FixedSpreadSecurityL fsec;
|
||||
set_security_common(sec, fsec);
|
||||
vector<uint32_t> leg_exchange_ids;
|
||||
leg_exchange_ids.reserve(sec.legs().size());
|
||||
|
||||
onixs::PRICE min_tick = sec.minPriceIncrement();
|
||||
|
||||
int8_t tick_rule;
|
||||
if (sec.tickRule(tick_rule)) {
|
||||
decode_ticks(tick_rule, double_of_price(min_tick, 0), fsec);
|
||||
}
|
||||
else {
|
||||
double mintick = double_of_price(min_tick, 0);
|
||||
fsec.low_tick = mintick;
|
||||
fsec.high_tick = mintick;
|
||||
fsec.price_break = 0;
|
||||
}
|
||||
|
||||
fsec.set_leg_count(sec.legs().size());
|
||||
for(size_t x = 0; x < fsec.leg_count(); ++x) {
|
||||
const auto& leg = sec.legs()[x];
|
||||
onixs::PRICE price;
|
||||
if (leg.legPrice(price)) {
|
||||
fsec.legs[x].leg_price = double_of_price(price, fsec.display_factor);
|
||||
}
|
||||
onixs::DecimalQty qty;
|
||||
if (leg.legOptionDelta(qty)) {
|
||||
fsec.legs[x].leg_delta = double_of_price(qty, 0);
|
||||
}
|
||||
fsec.legs[x].leg_quantity = leg.legRatioQty();
|
||||
fsec.legs[x].leg_side = leg.legSide() == onixs::LegSide::BuySide ? api::SIDE_BUY : api::SIDE_SELL;
|
||||
leg_exchange_ids.push_back(leg.legSecurityId());
|
||||
}
|
||||
add_security(fsec);
|
||||
_index.add_spread_legs(fsec.numeric_id, leg_exchange_ids);
|
||||
}
|
||||
|
||||
void MDP3Listener::onBookUpdate(onixs::Handler&, const onixs::Security& sec, const onixs::DirectBook& book) {
|
||||
if (_booktype == CMEOptions::BOOKTYPE_DIRECT) {
|
||||
onBookUpdate(sec, book);
|
||||
@@ -349,48 +129,28 @@ void MDP3Listener::onBookUpdate(onixs::Handler&, const onixs::Security& sec, con
|
||||
//
|
||||
// Trades
|
||||
|
||||
void on_trade(SecurityData* data, const onixs::PRICE& price, int size, const onixs::Timestamp& onixs_date) {
|
||||
AggressorSide map_aggressor(onixs::AggressorSide::Enum agside) {
|
||||
switch(agside) {
|
||||
default:
|
||||
case onixs::AggressorSide::NoAggressor:
|
||||
return AGGRESSOR_SIDE_NONE;
|
||||
case onixs::AggressorSide::Buy:
|
||||
return AGGRESSOR_SIDE_BUY;
|
||||
case onixs::AggressorSide::Sell:
|
||||
return AGGRESSOR_SIDE_SELL;
|
||||
}
|
||||
}
|
||||
|
||||
void on_trade(SecurityData* data, const onixs::Decimal& price, int size, onixs::AggressorSide::Enum agside, const onixs::Timestamp& onixs_date) {
|
||||
auto ts = utils::TimeStamp::now();
|
||||
PriceSize ps = pricesize_of_price_size(price, data->handle.security().display_factor, size);
|
||||
auto date = ll_of_timestamp(onixs_date);
|
||||
data->handle.add_trade(ts, ps, date, data->electronic_volume + size);
|
||||
}
|
||||
|
||||
void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& arg) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onTrade.recovery", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != NULL) {
|
||||
int size = 0;
|
||||
arg.entry().entrySize(size);
|
||||
onixs::PRICE price;
|
||||
arg.entry().entryPx(price);
|
||||
on_trade(data, price, size, arg.message().transactTime());
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const onixs::TradeSummaryArgs& arg) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onTrade.summary", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != NULL) {
|
||||
auto size = arg.entry().entrySize();
|
||||
onixs::PRICE price = arg.entry().entryPx();
|
||||
on_trade(data, price, size, arg.message().transactTime());
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const onixs::TradeArgs& arg) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onTrade.trade_args", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
if (arg.entry().updateAction() == onixs::UpdateAction::New) {
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
int size = arg.entry().entrySize();
|
||||
onixs::PRICE price = arg.entry().entryPx();
|
||||
on_trade(data, price, size, arg.message().transactTime());
|
||||
}
|
||||
}
|
||||
data->handle.add_trade(ts, ps, date, map_aggressor(agside), data->electronic_volume + size);
|
||||
}
|
||||
|
||||
//
|
||||
// RFQ
|
||||
void MDP3Listener::onQuoteRequest(onixs::Handler&, const onixs::Security& sec, const onixs::QuoteRequestRelatedSymArgs& arg) {
|
||||
void MDP3Listener::onQuoteRequest(onixs::Handler&, const onixs::Security& sec, const onixs::QuoteRequest39RelatedSymArgs& arg) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onQuoteRequest", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
@@ -439,7 +199,7 @@ void MDP3Listener::onQuoteRequest(onixs::Handler&, const onixs::Security& sec, c
|
||||
|
||||
//
|
||||
// Volume
|
||||
void MDP3Listener::onElectronicVolume(onixs::Handler&, const onixs::Security& sec, const onixs::VolumeArgs& args) {
|
||||
void MDP3Listener::onElectronicVolume(onixs::Handler&, const onixs::Security& sec, const onixs::Volume37Args& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onElectronicVolume.volume", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
@@ -449,162 +209,6 @@ void MDP3Listener::onElectronicVolume(onixs::Handler&, const onixs::Security& se
|
||||
data->electronic_volume = args.entry().entrySize();
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onElectronicVolume(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onElectronicVolume.recovery", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data) {
|
||||
int32_t size;
|
||||
if (args.entry().entrySize(size)) {
|
||||
data->electronic_volume = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
void MDP3Listener::onSettlementPrice(onixs::Handler&, const onixs::Security& sec, const onixs::DailyStatisticsArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onSettlementPrice.daily", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
onixs::PRICE price;
|
||||
onixs::Timestamp time;
|
||||
args.entry().entryPx(price);
|
||||
args.entry().tradingReferenceDate(time);
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_settlement(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(price, display_factor),
|
||||
date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onSettlementPrice(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onSettlementPrice.recovery", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
onixs::PRICE price;
|
||||
onixs::Timestamp time;
|
||||
args.entry().entryPx(price);
|
||||
args.entry().tradingReferenceDate(time);
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_settlement(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(price, display_factor),
|
||||
date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onOpenInterest(onixs::Handler&, const onixs::Security& sec, const onixs::DailyStatisticsArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onOpenInterest.daily", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
onixs::PRICE price;
|
||||
args.entry().entryPx(price);
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_open_interest(utils::TimeStamp::now(), double_of_price(price, display_factor));
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onOpenInterest(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onOpenInterest.recovery", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
onixs::PRICE price;
|
||||
args.entry().entryPx(price);
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_open_interest(utils::TimeStamp::now(), double_of_price(price, display_factor));
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onOpeningPrice(onixs::Handler&, const onixs::Security& sec, const onixs::SessionStatisticsArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onOpeningPrice.session", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
onixs::PRICE price = args.entry().entryPx();
|
||||
onixs::Timestamp time = args.message().transactTime();
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_opening(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(price, display_factor),
|
||||
date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onOpeningPrice(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onOpeningPrice.recovery", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
onixs::PRICE price;
|
||||
args.entry().entryPx(price);
|
||||
onixs::Timestamp time;
|
||||
args.entry().tradingReferenceDate(time);
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_opening(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(price, display_factor),
|
||||
date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onHighTradePrice(onixs::Handler&, const onixs::Security& sec, const onixs::SessionStatisticsArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onHighTradePrice.session", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_session_high(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(args.entry().entryPx(), display_factor));
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onHighTradePrice(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onHighTradePrice.recovery", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
onixs::PRICE price;
|
||||
args.entry().entryPx(price);
|
||||
data->handle.update_session_high(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(price, display_factor));
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onLowTradePrice(onixs::Handler&, const onixs::Security& sec, const onixs::SessionStatisticsArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onLowTradePrice.session", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_session_low(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(args.entry().entryPx(), display_factor));
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onLowTradePrice(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onLowTradePrice.recovery", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
onixs::PRICE price;
|
||||
args.entry().entryPx(price);
|
||||
data->handle.update_session_low(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(price, display_factor));
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onHighestBid(onixs::Handler&, const onixs::Security& sec, const onixs::SessionStatisticsArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onHighestBid.session", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_session_high_bid(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(args.entry().entryPx(), display_factor));
|
||||
}
|
||||
}
|
||||
void MDP3Listener::onLowestOffer(onixs::Handler&, const onixs::Security& sec, const onixs::SessionStatisticsArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onLowestOffer.session", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_session_low_offer(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(args.entry().entryPx(), display_factor));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Non-onixs calls
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include "CMESecurityIndex.h"
|
||||
#include "server/sparkd/tracing/bts_trace.h"
|
||||
|
||||
#include "MDP3ListenerHelpers.h"
|
||||
#include "onixs.h"
|
||||
|
||||
#include <tbb/concurrent_queue.h>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
@@ -14,11 +16,6 @@
|
||||
namespace bts {
|
||||
namespace md {
|
||||
|
||||
inline PriceSize pricesize_of_price_size(const onixs::PRICE price, int8_t display_factor, Size size) {
|
||||
PriceSize result(price.mantissa(), price.exponent() + display_factor, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
class MDP3Listener :
|
||||
public onixs::HandlerListener,
|
||||
public onixs::MarketDataListener,
|
||||
@@ -54,9 +51,21 @@ public:
|
||||
|
||||
//
|
||||
// Security discovery
|
||||
void onDefinition(onixs::Handler&, const onixs::Security&, const onixs::InstrumentDefinitionFutureArgs&) override;
|
||||
void onDefinition(onixs::Handler&, const onixs::Security&, const onixs::InstrumentDefinitionOptionArgs&) override;
|
||||
void onDefinition(onixs::Handler&, const onixs::Security&, const onixs::InstrumentDefinitionSpreadArgs&) override;
|
||||
template <typename InstrumentDefinitionFuture>
|
||||
void onFuture(onixs::Handler&, const onixs::Security&, const InstrumentDefinitionFuture&);
|
||||
template <typename InstrumentDefinition>
|
||||
void onOption(onixs::Handler&, const onixs::Security&, const InstrumentDefinition&);
|
||||
template <typename InstrumentDefinition>
|
||||
void onSpread(onixs::Handler&, const onixs::Security&, const InstrumentDefinition&);
|
||||
|
||||
void onDefinition(onixs::Handler& h, const onixs::Security& s, const onixs::InstrumentDefinitionFuture27Args& def) override { onFuture(h, s, def); }
|
||||
void onDefinition(onixs::Handler& h, const onixs::Security& s, const onixs::InstrumentDefinitionFuture54Args& def) override { onFuture(h, s, def); }
|
||||
|
||||
void onDefinition(onixs::Handler& h, const onixs::Security& s, const onixs::InstrumentDefinitionOption41Args& def) override { onOption(h, s, def); }
|
||||
void onDefinition(onixs::Handler& h, const onixs::Security& s, const onixs::InstrumentDefinitionOption55Args& def) override { onOption(h, s, def); }
|
||||
|
||||
void onDefinition(onixs::Handler& h, const onixs::Security& s, const onixs::InstrumentDefinitionSpread29Args& def) override { onSpread(h, s, def); }
|
||||
void onDefinition(onixs::Handler& h, const onixs::Security& s, const onixs::InstrumentDefinitionSpread56Args& def) override { onSpread(h, s, def); }
|
||||
|
||||
//
|
||||
// Book
|
||||
@@ -66,79 +75,74 @@ public:
|
||||
void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::ConsolidatedBook&) override;
|
||||
|
||||
template <class TBook>
|
||||
void onBookUpdate(const onixs::Security& sec, const TBook& book) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
|
||||
auto data = _index.get_security(sec.id());
|
||||
if (data == nullptr) return;
|
||||
bts_assert(data->id == (uint32_t)sec.id());
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
|
||||
// Put the data into the cache. Flush at the end of the packet.
|
||||
auto key = data->handle.key();
|
||||
auto find_iter = std::find_if(_book_updates.begin(), _book_updates.end(), [key](const BookData& entry) {
|
||||
return key == entry.handle.key();
|
||||
});
|
||||
bool found = find_iter != _book_updates.end();
|
||||
if (!found) {
|
||||
_book_updates.emplace_back(data->handle);
|
||||
}
|
||||
BookData& cache = found ? *find_iter : _book_updates.back();
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = book.bids().size();
|
||||
size_t asks_size = book.offers().size();
|
||||
if (!data->handle.has_depth()) {
|
||||
bids_size = std::min(bids_size, size_t { 1 });
|
||||
asks_size = std::min(asks_size, size_t { 1 });
|
||||
}
|
||||
for (size_t x = 0; x < bids_size; ++x) {
|
||||
cache.bids[x] = pricesize_of_price_size(book.bids()[x].price(), display_factor, book.bids()[x].quantity());
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x) {
|
||||
cache.asks[x] = pricesize_of_price_size(book.offers()[x].price(), display_factor, book.offers()[x].quantity());
|
||||
}
|
||||
// NaN out the remaining depth
|
||||
for (size_t x = bids_size; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
cache.bids[x] = PriceSize::NO_MARKET;
|
||||
}
|
||||
for (size_t x = asks_size; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
cache.asks[x] = PriceSize::NO_MARKET;
|
||||
}
|
||||
}
|
||||
void onBookUpdate(const onixs::Security& sec, const TBook& book);
|
||||
|
||||
//
|
||||
// Trades
|
||||
void onTrade(onixs::Handler&, const onixs::Security&, const onixs::RecoveryEntryArgs&) override;
|
||||
void onTrade(onixs::Handler&, const onixs::Security&, const onixs::TradeArgs&) override;
|
||||
void onTrade(onixs::Handler&, const onixs::Security&, const onixs::TradeSummaryArgs&) override;
|
||||
template <typename TradeRecovery>
|
||||
void onTradeRecovery(const onixs::Security&, const TradeRecovery&);
|
||||
void onTrade(onixs::Handler&, const onixs::Security& s, const onixs::Recovery52EntryArgs& arg) override { onTradeRecovery(s, arg); }
|
||||
void onTrade(onixs::Handler&, const onixs::Security& s, const onixs::Recovery38EntryArgs& arg) override { onTradeRecovery(s, arg); }
|
||||
|
||||
template <typename Trade>
|
||||
void onTradeSummary(const onixs::Security&, const Trade&);
|
||||
void onTrade(onixs::Handler&, const onixs::Security& s, const onixs::TradeSummary48Args& arg) override { onTradeSummary(s, arg); }
|
||||
void onTrade(onixs::Handler&, const onixs::Security& s, const onixs::TradeSummary42Args& arg) override { onTradeSummary(s, arg); }
|
||||
|
||||
//
|
||||
// RFQ
|
||||
void onQuoteRequest(onixs::Handler&, const onixs::Security&, const onixs::QuoteRequestRelatedSymArgs&) override;
|
||||
void onQuoteRequest(onixs::Handler&, const onixs::Security&, const onixs::QuoteRequest39RelatedSymArgs&) override;
|
||||
|
||||
//
|
||||
// Volume
|
||||
void onElectronicVolume(onixs::Handler&, const onixs::Security&, const onixs::VolumeArgs&) override;
|
||||
void onElectronicVolume(onixs::Handler&, const onixs::Security&, const onixs::RecoveryEntryArgs&) override;
|
||||
void onElectronicVolume(onixs::Handler&, const onixs::Security&, const onixs::Volume37Args&) override;
|
||||
template <typename RecoveryEntry>
|
||||
void onElectronicVolume(const onixs::Security&, const RecoveryEntry&);
|
||||
void onElectronicVolume(onixs::Handler&, const onixs::Security& s, const onixs::Recovery52EntryArgs& arg) override { onElectronicVolume(s, arg); }
|
||||
void onElectronicVolume(onixs::Handler&, const onixs::Security& s, const onixs::Recovery38EntryArgs& arg) override { onElectronicVolume(s, arg); }
|
||||
|
||||
//
|
||||
// Misc
|
||||
void onSettlementPrice(onixs::Handler&, const onixs::Security&, const onixs::DailyStatisticsArgs&) override;
|
||||
void onSettlementPrice(onixs::Handler&, const onixs::Security&, const onixs::RecoveryEntryArgs&) override;
|
||||
void onOpenInterest(onixs::Handler&, const onixs::Security&, const onixs::DailyStatisticsArgs&) override;
|
||||
void onOpenInterest(onixs::Handler&, const onixs::Security&, const onixs::RecoveryEntryArgs&) override;
|
||||
void onOpeningPrice(onixs::Handler&, const onixs::Security&, const onixs::SessionStatisticsArgs&) override;
|
||||
void onOpeningPrice(onixs::Handler&, const onixs::Security&, const onixs::RecoveryEntryArgs&) override;
|
||||
void onHighTradePrice(onixs::Handler&, const onixs::Security&, const onixs::SessionStatisticsArgs&) override;
|
||||
void onHighTradePrice(onixs::Handler&, const onixs::Security&, const onixs::RecoveryEntryArgs&) override;
|
||||
void onLowTradePrice(onixs::Handler&, const onixs::Security&, const onixs::SessionStatisticsArgs&) override;
|
||||
void onLowTradePrice(onixs::Handler&, const onixs::Security&, const onixs::RecoveryEntryArgs&) override;
|
||||
template <typename StatisticsArgs>
|
||||
void onSettlementPrice(const onixs::Security& sec, const StatisticsArgs& args);
|
||||
void onSettlementPrice(onixs::Handler&, const onixs::Security& s, const onixs::DailyStatistics49Args& arg) override { onSettlementPrice(s, arg); }
|
||||
void onSettlementPrice(onixs::Handler&, const onixs::Security& s, const onixs::DailyStatistics33Args& arg) override { onSettlementPrice(s, arg); }
|
||||
void onSettlementPrice(onixs::Handler&, const onixs::Security& s, const onixs::Recovery52EntryArgs& arg) override { onSettlementPrice(s, arg); }
|
||||
void onSettlementPrice(onixs::Handler&, const onixs::Security& s, const onixs::Recovery38EntryArgs& arg) override { onSettlementPrice(s, arg); }
|
||||
|
||||
void onHighestBid(onixs::Handler&, const onixs::Security&, const onixs::SessionStatisticsArgs&) override;
|
||||
void onLowestOffer(onixs::Handler&, const onixs::Security&, const onixs::SessionStatisticsArgs&) override;
|
||||
|
||||
template <typename InterestArgs>
|
||||
void onOpenInterest(const onixs::Security& sec, const InterestArgs& args);
|
||||
void onOpenInterest(onixs::Handler&, const onixs::Security& s, const onixs::DailyStatistics49Args& arg) override { onOpenInterest(s, arg); }
|
||||
void onOpenInterest(onixs::Handler&, const onixs::Security& s, const onixs::DailyStatistics33Args& arg) override { onOpenInterest(s, arg); }
|
||||
void onOpenInterest(onixs::Handler&, const onixs::Security& s, const onixs::Recovery52EntryArgs& arg) override { onOpenInterest(s, arg); }
|
||||
void onOpenInterest(onixs::Handler&, const onixs::Security& s, const onixs::Recovery38EntryArgs& arg) override { onOpenInterest(s, arg); }
|
||||
|
||||
template <typename SessionArgs>
|
||||
void onSessionOpeningPrice(const onixs::Security&, const SessionArgs&);
|
||||
template <typename RecoveryArgs>
|
||||
void onRecoveryOpeningPrice(const onixs::Security& sec, const RecoveryArgs& args);
|
||||
void onOpeningPrice(onixs::Handler&, const onixs::Security& s, const onixs::SessionStatistics51Args& arg) override { onSessionOpeningPrice(s, arg); }
|
||||
void onOpeningPrice(onixs::Handler&, const onixs::Security& s, const onixs::SessionStatistics35Args& arg) override { onSessionOpeningPrice(s, arg); }
|
||||
void onOpeningPrice(onixs::Handler&, const onixs::Security& s, const onixs::Recovery52EntryArgs& arg) override { onRecoveryOpeningPrice(s, arg); }
|
||||
void onOpeningPrice(onixs::Handler&, const onixs::Security& s, const onixs::Recovery38EntryArgs& arg) override { onRecoveryOpeningPrice(s, arg); }
|
||||
|
||||
template <typename SessionArgs>
|
||||
void onSessionHighTradePrice(const onixs::Security&, const SessionArgs&);
|
||||
template <typename RecoveryArgs>
|
||||
void onRecoveryHighTradePrice(const onixs::Security&, const RecoveryArgs&);
|
||||
void onHighTradePrice(onixs::Handler&, const onixs::Security& s, const onixs::SessionStatistics51Args& arg) override { onSessionHighTradePrice(s, arg); }
|
||||
void onHighTradePrice(onixs::Handler&, const onixs::Security& s, const onixs::SessionStatistics35Args& arg) override { onSessionHighTradePrice(s, arg); }
|
||||
void onHighTradePrice(onixs::Handler&, const onixs::Security& s, const onixs::Recovery52EntryArgs& arg) override { onRecoveryHighTradePrice(s, arg); }
|
||||
void onHighTradePrice(onixs::Handler&, const onixs::Security& s, const onixs::Recovery38EntryArgs& arg) override { onRecoveryHighTradePrice(s, arg); }
|
||||
|
||||
template <typename SessionArgs>
|
||||
void onSessionLowTradePrice(const onixs::Security&, const SessionArgs&);
|
||||
template <typename RecoveryArgs>
|
||||
void onRecoveryLowTradePrice(const onixs::Security&, const RecoveryArgs&);
|
||||
void onLowTradePrice(onixs::Handler&, const onixs::Security& s, const onixs::SessionStatistics51Args& arg) override { onSessionLowTradePrice(s, arg); }
|
||||
void onLowTradePrice(onixs::Handler&, const onixs::Security& s, const onixs::SessionStatistics35Args& arg) override { onSessionLowTradePrice(s, arg); }
|
||||
void onLowTradePrice(onixs::Handler&, const onixs::Security& s, const onixs::Recovery52EntryArgs& arg) override { onRecoveryLowTradePrice(s, arg); }
|
||||
void onLowTradePrice(onixs::Handler&, const onixs::Security& s, const onixs::Recovery38EntryArgs& arg) override { onRecoveryLowTradePrice(s, arg); }
|
||||
|
||||
//
|
||||
// Discover securities
|
||||
@@ -190,4 +194,345 @@ private:
|
||||
|
||||
};
|
||||
|
||||
//////////////////////////////////////////
|
||||
// Template Member Function Definitions
|
||||
//////////////////////////////////////////
|
||||
|
||||
template <class Definition>
|
||||
void set_security_common(const Definition& def, FixedSecurity& fsec) {
|
||||
fsec.numeric_id = def.securityId();
|
||||
fsec.symbol(def.asset().items(), def.asset().size());
|
||||
fsec.cfi_code(def.cfiCode().items(), def.cfiCode().size());
|
||||
fsec.alt_symbol(def.securityGroup().items(), def.securityGroup().size());
|
||||
fsec.security_desc(def.symbol().items(), def.symbol().size());
|
||||
fsec.exchange = api::Venue::VENUE_GLBX;
|
||||
// tickRule not present in futures
|
||||
fsec.cme_market_segment_id = def.marketSegmentId();
|
||||
fsec.display_factor = def.displayFactor().exponent() + floorlog10(def.displayFactor().mantissa());
|
||||
|
||||
def.mainFraction(fsec.cme_main_fraction);
|
||||
def.subFraction(fsec.cme_sub_fraction);
|
||||
def.priceDisplayFormat(fsec.cme_price_display_format);
|
||||
}
|
||||
|
||||
|
||||
template <typename InstrumentDefinition>
|
||||
void MDP3Listener::onFuture(onixs::Handler&, const onixs::Security& osec, const InstrumentDefinition& def) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onDefinition.future", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
const auto& sec = def.message();
|
||||
auto exchange_id = sec.securityId();
|
||||
// LOG(md, Info, "onDefinition future {} {}",
|
||||
// std::string(sec.asset().items(), sec.asset().size()),
|
||||
// std::string(sec.symbol().items(), sec.symbol().size()));
|
||||
|
||||
if (filter_security(exchange_id, sec) != ADD) return;
|
||||
bts_assert(exchange_id == osec.id());
|
||||
|
||||
FixedFutureSecurity fsec;
|
||||
|
||||
set_security_common(sec, fsec);
|
||||
fsec.price_break = 0;
|
||||
// low/high tick are stored non-premultiplied and multiplied in conversion to api::Secutity
|
||||
double min_tick = double_of_price(sec.minPriceIncrement(), 0);
|
||||
fsec.low_tick = fsec.high_tick = min_tick;
|
||||
onixs::Decimal min_tick_amount;
|
||||
sec.minPriceIncrementAmount(min_tick_amount);
|
||||
double min_tick_dbl = double_of_price(min_tick_amount, 0);
|
||||
fsec.cash_per_point = min_tick_dbl / min_tick / pow10(fsec.display_factor);
|
||||
auto expiration = date_of_maturity(sec);
|
||||
fsec.expiration_year = expiration.year;
|
||||
fsec.expiration_month = expiration.month;
|
||||
fsec.expiration_day = expiration.day;
|
||||
add_security(fsec);
|
||||
}
|
||||
|
||||
template <typename InstrumentDefinition>
|
||||
void MDP3Listener::onOption(onixs::Handler&, const onixs::Security&, const InstrumentDefinition& def) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onDefinition.option", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
const auto& sec = def.message();
|
||||
auto exchange_id = sec.securityId();
|
||||
// LOG(Info, MarketData) << "onDefinition option " << exchange_id;
|
||||
if (filter_security(exchange_id, sec) != ADD) return;
|
||||
|
||||
FixedOptionSecurity fsec;
|
||||
set_security_common(sec, fsec);
|
||||
auto expiration = date_of_maturity(sec);
|
||||
fsec.expiration_year = expiration.year;
|
||||
fsec.expiration_month = expiration.month;
|
||||
fsec.expiration_day = expiration.day;
|
||||
|
||||
onixs::Decimal strike_price;
|
||||
bts_assert(sec.strikePrice(strike_price));
|
||||
fsec.strike = double_of_price(strike_price, fsec.display_factor);
|
||||
|
||||
onixs::Decimal min_tick, min_tick_amount;
|
||||
sec.minPriceIncrement(min_tick);
|
||||
sec.minPriceIncrementAmount(min_tick_amount);
|
||||
|
||||
int8_t tick_rule;
|
||||
if (sec.tickRule(tick_rule)) {
|
||||
decode_ticks(tick_rule, double_of_price(min_tick, 0), fsec);
|
||||
}
|
||||
fsec.cash_per_point = double_of_price(min_tick_amount, 0) / fsec.low_tick / pow10(fsec.display_factor);
|
||||
|
||||
fsec.call_put = sec.putOrCall() == onixs::PutOrCall::Call ? api::PUTORCALL_CALL : api::PUTORCALL_PUT;
|
||||
|
||||
add_security(fsec);
|
||||
}
|
||||
|
||||
// min_tick specializations
|
||||
inline onixs::Decimal tick_rule(const onixs::InstrumentDefinitionSpread29& sec) {
|
||||
return sec.minPriceIncrement();
|
||||
}
|
||||
inline onixs::Decimal tick_rule(const onixs::InstrumentDefinitionSpread56& sec) {
|
||||
onixs::Decimal min_tick;
|
||||
sec.minPriceIncrement(min_tick);
|
||||
return min_tick;
|
||||
}
|
||||
|
||||
template <typename InstrumentDefinition>
|
||||
void MDP3Listener::onSpread(onixs::Handler&, const onixs::Security&, const InstrumentDefinition& spread_args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onDefinition.spread", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
const auto& sec = spread_args.message();
|
||||
auto exchange_id = sec.securityId();
|
||||
// LOG(md, Info, "onDefinition spread {} {}",
|
||||
// std::string(sec.asset().items(), sec.asset().size()),
|
||||
// std::string(sec.symbol().items(), sec.symbol().size()));
|
||||
if (filter_security(exchange_id, sec) != ADD) return;
|
||||
|
||||
// Filter out any already expired spreads
|
||||
auto expiration = date_of_maturity(sec);
|
||||
if (expiration < datetime::today()) return;
|
||||
|
||||
FixedSpreadSecurityL fsec;
|
||||
set_security_common(sec, fsec);
|
||||
std::vector<uint32_t> leg_exchange_ids;
|
||||
leg_exchange_ids.reserve(sec.legs().size());
|
||||
|
||||
onixs::Decimal min_tick = tick_rule(sec);
|
||||
|
||||
int8_t tick_rule;
|
||||
if (sec.tickRule(tick_rule)) {
|
||||
decode_ticks(tick_rule, double_of_price(min_tick, 0), fsec);
|
||||
}
|
||||
else {
|
||||
double mintick = double_of_price(min_tick, 0);
|
||||
fsec.low_tick = mintick;
|
||||
fsec.high_tick = mintick;
|
||||
fsec.price_break = 0;
|
||||
}
|
||||
|
||||
fsec.set_leg_count(sec.legs().size());
|
||||
for(size_t x = 0; x < fsec.leg_count(); ++x) {
|
||||
const auto& leg = sec.legs()[x];
|
||||
onixs::Decimal price;
|
||||
if (leg.legPrice(price)) {
|
||||
fsec.legs[x].leg_price = double_of_price(price, fsec.display_factor);
|
||||
}
|
||||
onixs::Decimal qty;
|
||||
if (leg.legOptionDelta(qty)) {
|
||||
fsec.legs[x].leg_delta = double_of_price(qty, 0);
|
||||
}
|
||||
fsec.legs[x].leg_quantity = leg.legRatioQty();
|
||||
fsec.legs[x].leg_side = leg.legSide() == onixs::LegSide::BuySide ? api::SIDE_BUY : api::SIDE_SELL;
|
||||
leg_exchange_ids.push_back(leg.legSecurityId());
|
||||
}
|
||||
add_security(fsec);
|
||||
_index.add_spread_legs(fsec.numeric_id, leg_exchange_ids);
|
||||
}
|
||||
|
||||
|
||||
template <class TBook>
|
||||
void MDP3Listener::onBookUpdate(const onixs::Security& sec, const TBook& book) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
|
||||
auto data = _index.get_security(sec.id());
|
||||
if (data == nullptr) return;
|
||||
bts_assert(data->id == (uint32_t)sec.id());
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
|
||||
// Put the data into the cache. Flush at the end of the packet.
|
||||
auto key = data->handle.key();
|
||||
auto find_iter = std::find_if(_book_updates.begin(), _book_updates.end(), [key](const BookData& entry) {
|
||||
return key == entry.handle.key();
|
||||
});
|
||||
bool found = find_iter != _book_updates.end();
|
||||
if (!found) {
|
||||
_book_updates.emplace_back(data->handle);
|
||||
}
|
||||
BookData& cache = found ? *find_iter : _book_updates.back();
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = book.bids().size();
|
||||
size_t asks_size = book.offers().size();
|
||||
if (!data->handle.has_depth()) {
|
||||
bids_size = std::min(bids_size, size_t { 1 });
|
||||
asks_size = std::min(asks_size, size_t { 1 });
|
||||
}
|
||||
for (size_t x = 0; x < bids_size; ++x) {
|
||||
cache.bids[x] = pricesize_of_price_size(book.bids()[x].price(), display_factor, book.bids()[x].quantity());
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x) {
|
||||
cache.asks[x] = pricesize_of_price_size(book.offers()[x].price(), display_factor, book.offers()[x].quantity());
|
||||
}
|
||||
// NaN out the remaining depth
|
||||
for (size_t x = bids_size; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
cache.bids[x] = PriceSize::NO_MARKET;
|
||||
}
|
||||
for (size_t x = asks_size; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
cache.asks[x] = PriceSize::NO_MARKET;
|
||||
}
|
||||
}
|
||||
|
||||
void on_trade(SecurityData* data, const onixs::Decimal& price, int size, onixs::AggressorSide::Enum agside, const onixs::Timestamp& onixs_date);
|
||||
|
||||
template <typename TradeRecovery>
|
||||
void MDP3Listener::onTradeRecovery(const onixs::Security& sec, const TradeRecovery& arg) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onTrade.recovery", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != NULL) {
|
||||
int size = 0;
|
||||
arg.entry().entrySize(size);
|
||||
onixs::Decimal price;
|
||||
arg.entry().entryPx(price);
|
||||
on_trade(data, price, size, onixs::AggressorSide::NoAggressor, arg.message().transactTime());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TradeSummary>
|
||||
void MDP3Listener::onTradeSummary(const onixs::Security& sec, const TradeSummary& arg) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onTrade.summary", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != NULL) {
|
||||
auto size = arg.entry().entrySize();
|
||||
onixs::Decimal price = arg.entry().entryPx();
|
||||
onixs::AggressorSide::Enum agside = onixs::AggressorSide::NoAggressor;
|
||||
arg.entry().aggressorSide(agside);
|
||||
on_trade(data, price, size, agside, arg.message().transactTime());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename RecoveryEntryArgs>
|
||||
void MDP3Listener::onElectronicVolume(const onixs::Security& sec, const RecoveryEntryArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onElectronicVolume.recovery", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data) {
|
||||
int32_t size;
|
||||
if (args.entry().entrySize(size)) {
|
||||
data->electronic_volume = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename StatisticsArgs>
|
||||
void MDP3Listener::onSettlementPrice(const onixs::Security& sec, const StatisticsArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onSettlementPrice.daily", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
onixs::Decimal price;
|
||||
onixs::Timestamp time;
|
||||
args.entry().entryPx(price);
|
||||
args.entry().tradingReferenceDate(time);
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_settlement(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(price, display_factor),
|
||||
date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename InterestArgs>
|
||||
void MDP3Listener::onOpenInterest(const onixs::Security& sec, const InterestArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onOpenInterest.daily", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
onixs::Decimal price;
|
||||
args.entry().entryPx(price);
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_open_interest(utils::TimeStamp::now(), double_of_price(price, display_factor));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename SessionArgs>
|
||||
void MDP3Listener::onSessionOpeningPrice(const onixs::Security& sec, const SessionArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onOpeningPrice.session", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
onixs::Decimal price = args.entry().entryPx();
|
||||
onixs::Timestamp time = args.message().transactTime();
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_opening(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(price, display_factor),
|
||||
date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
template <typename RecoveryArgs>
|
||||
void MDP3Listener::onRecoveryOpeningPrice(const onixs::Security& sec, const RecoveryArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onOpeningPrice.recovery", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
onixs::Decimal price;
|
||||
args.entry().entryPx(price);
|
||||
onixs::Timestamp time;
|
||||
args.entry().tradingReferenceDate(time);
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_opening(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(price, display_factor),
|
||||
date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename SessionArgs>
|
||||
void MDP3Listener::onSessionHighTradePrice(const onixs::Security& sec, const SessionArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onHighTradePrice.session", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_session_high(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(args.entry().entryPx(), display_factor));
|
||||
}
|
||||
}
|
||||
template <typename RecoveryArgs>
|
||||
void MDP3Listener::onRecoveryHighTradePrice(const onixs::Security& sec, const RecoveryArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onHighTradePrice.recovery", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
onixs::Decimal price;
|
||||
args.entry().entryPx(price);
|
||||
data->handle.update_session_high(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(price, display_factor));
|
||||
}
|
||||
}
|
||||
template <typename SessionArgs>
|
||||
void MDP3Listener::onSessionLowTradePrice(const onixs::Security& sec, const SessionArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onLowTradePrice.session", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
data->handle.update_session_low(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(args.entry().entryPx(), display_factor));
|
||||
}
|
||||
}
|
||||
template <typename RecoveryArgs>
|
||||
void MDP3Listener::onRecoveryLowTradePrice(const onixs::Security& sec, const RecoveryArgs& args) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onLowTradePrice.recovery", _latest_sequence, (uint16_t) _latest_sequence, 0);
|
||||
auto* data = _index.get_security(sec.id());
|
||||
if (data != nullptr) {
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
onixs::Decimal price;
|
||||
args.entry().entryPx(price);
|
||||
data->handle.update_session_low(
|
||||
utils::TimeStamp::now(),
|
||||
double_of_price(price, display_factor));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}}//namespace
|
||||
@@ -0,0 +1,112 @@
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/md/coconut2.h"
|
||||
|
||||
#include "onixs.h"
|
||||
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
|
||||
namespace bts {
|
||||
namespace md {
|
||||
|
||||
inline PriceSize pricesize_of_price_size(const onixs::Decimal price, int8_t display_factor, Size size) {
|
||||
PriceSize result(price.mantissa(), price.exponent() + display_factor, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class Price>
|
||||
double double_of_price(const Price& price, int8_t display_factor) {
|
||||
// order of multiplication is important because of floating point error
|
||||
return (price.mantissa() * pow10(price.exponent())) * pow10(display_factor);
|
||||
}
|
||||
|
||||
inline void decode_ticks(int vtt, double mintick, FixedSecurity& dest) {
|
||||
switch(vtt) {
|
||||
default:
|
||||
case 0:
|
||||
dest.low_tick = mintick;
|
||||
dest.high_tick = mintick;
|
||||
dest.price_break = 0;
|
||||
break;
|
||||
case 1:
|
||||
dest.price_break = 500;
|
||||
dest.low_tick = 5;
|
||||
dest.high_tick = 10;
|
||||
break;
|
||||
case 2:
|
||||
dest.price_break = 5;
|
||||
dest.low_tick = 0.5;
|
||||
dest.high_tick = 1;
|
||||
break;
|
||||
case 3:
|
||||
dest.price_break = 10;
|
||||
dest.low_tick = 1;
|
||||
dest.high_tick = 2;
|
||||
break;
|
||||
case 4:
|
||||
dest.price_break = 500;
|
||||
dest.low_tick = 5;
|
||||
dest.high_tick = 25;
|
||||
break;
|
||||
case 10:
|
||||
dest.price_break = 300;
|
||||
dest.low_tick = 5;
|
||||
dest.high_tick = 25;
|
||||
break;
|
||||
case 11:
|
||||
dest.price_break = 300;
|
||||
dest.low_tick = 5;
|
||||
dest.high_tick = 10;
|
||||
break;
|
||||
case 12:
|
||||
dest.price_break = 5;
|
||||
dest.low_tick = 0.25;
|
||||
dest.high_tick = 0.5;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline int floorlog10(int64_t m) {
|
||||
bts_assert(m >= 0);
|
||||
if (m < 10) return 0;
|
||||
if (m < 100) return 1;
|
||||
if (m < 1000) return 2;
|
||||
if (m < 10000) return 3;
|
||||
if (m < 100000) return 4;
|
||||
if (m < 1000000) return 5;
|
||||
if (m < 10000000) return 6;
|
||||
if (m < 100000000) return 7;
|
||||
if (m < 1000000000) return 8;
|
||||
if (m < 10000000000) return 9;
|
||||
if (m < 100000000000) return 10;
|
||||
if (m < 1000000000000) return 11;
|
||||
if (m < 10000000000000) return 12;
|
||||
if (m < 100000000000000) return 13;
|
||||
if (m < 1000000000000000) return 14;
|
||||
if (m < 10000000000000000) return 15;
|
||||
if (m < 100000000000000000) return 16;
|
||||
if (m < 1000000000000000000) return 17;
|
||||
return 19;
|
||||
}
|
||||
|
||||
inline datetime::LLDateTime ll_of_timestamp(const onixs::Timestamp& ts) {
|
||||
return datetime::ll_from_parts(ts.year(), (int)ts.month(), ts.day(), ts.hour(), ts.minute(), ts.second(), ts.millisecond());
|
||||
}
|
||||
|
||||
inline datetime::Date date_of_timestamp(const onixs::Timestamp& ts) {
|
||||
return datetime::Date(ts.year(), (int)ts.month(), ts.day());
|
||||
}
|
||||
|
||||
template <class Definition>
|
||||
datetime::Date date_of_maturity(const Definition& def) {
|
||||
for (const auto& event : def.events()) {
|
||||
if (event.eventType() == onixs::EventType::LastEligibleTradeDate) {
|
||||
return date_of_timestamp(event.eventTime());
|
||||
}
|
||||
}
|
||||
return datetime::Date();
|
||||
}
|
||||
|
||||
|
||||
}}//namespace
|
||||
@@ -299,6 +299,12 @@ unique_ptr<onixs::Handler> MDP3Main::prepare_handler(const string& channel_id) {
|
||||
loggerSettings.filename(log_path() + "/log_" + channel_id);
|
||||
loggerSettings.severityLevel((onixs::LogSeverity::Enum)options.onixs_log_mode());
|
||||
logger = new onixs::FileLogger(loggerSettings);
|
||||
if (options.onixs_log_mode() == CMEOptions::Debug) {
|
||||
// trace all the things
|
||||
settings.debugLogging().traceBooks(true);
|
||||
settings.debugLogging().traceFeeds(true);
|
||||
settings.debugLogging().traceMessages(onixs::MessageTracing::FixFormat);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -218,9 +218,14 @@ void ExecutionGateway::on_exe_report(
|
||||
: api::orderman::MANUAL_ORDER_INDICATOR_YES;
|
||||
str_to(msg.get<fix::OrderID>(), &data->exchange_order_id);
|
||||
|
||||
if (msg.has<fix::Price>())
|
||||
data->price = securities::decode_exchange_price(
|
||||
*sec, Price(msg.get<fix::Price>()));
|
||||
if (msg.has<fix::Price>()) {
|
||||
if (data->leg_security)
|
||||
data->price = securities::decode_exchange_price(
|
||||
*data->leg_security, Price(msg.get<fix::Price>()));
|
||||
else
|
||||
data->price = securities::decode_exchange_price(
|
||||
*sec, Price(msg.get<fix::Price>()));
|
||||
}
|
||||
else
|
||||
data->price = Price(os::nan());
|
||||
|
||||
|
||||
@@ -21,14 +21,14 @@ public:
|
||||
net_pos_new = net_pos + quantity.value();
|
||||
else if(side == api::Side::SIDE_SELL)
|
||||
net_pos_new = net_pos - quantity.value();
|
||||
if (net_pos_new > long_pos_limit){
|
||||
if (net_pos_new > long_pos_limit && side==api::Side::SIDE_BUY){
|
||||
out_res.ok = false;
|
||||
if (is_security_level)
|
||||
out_res.error_msg = fmt::format("Security Long Position (Max {} Current {})", long_pos_limit, net_pos);
|
||||
else
|
||||
out_res.error_msg = fmt::format("Product Long Position (Max {} Current {})", long_pos_limit, net_pos);
|
||||
}
|
||||
else if (net_pos_new < short_pos_limit){
|
||||
else if (net_pos_new < short_pos_limit && side==api::Side::SIDE_SELL){
|
||||
out_res.ok = false;
|
||||
if (is_security_level)
|
||||
out_res.error_msg = fmt::format("Security Short Position (Max {} Current {})", short_pos_limit, net_pos);
|
||||
|
||||
@@ -271,9 +271,17 @@ void IcebergOrder::on_child_order_update(OrderStateUpdateEvent const &ev) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ORDER_STATUS_CANCELED:
|
||||
LOG(OrderMan, Info,
|
||||
"Unexpected cancel of child {} in Iceberg Order {}. Canceling "
|
||||
"parent",
|
||||
ev.key.value(),
|
||||
key().value());
|
||||
transition(ORDER_STATUS_CANCELED);
|
||||
break;
|
||||
|
||||
case ORDER_STATUS_DISCONNECTED:
|
||||
case ORDER_STATUS_REJECTED_BY_SPARK:
|
||||
case ORDER_STATUS_CANCELED:
|
||||
case ORDER_STATUS_ERROR:
|
||||
me->child.clear();
|
||||
transition(ORDER_STATUS_ERROR, "Unexpected child order state");
|
||||
|
||||
@@ -588,6 +588,12 @@ void NativeOrder::on_exe_report(const NewExecutionReportEvent &er) {
|
||||
auto &state = me->state;
|
||||
auto &native_state = get_native_state(state);
|
||||
|
||||
if (er.data->leg_security) {
|
||||
LOG(OrderMan, Diag,
|
||||
"Ignoring leg execution report for order {}", key().value());
|
||||
return;
|
||||
}
|
||||
|
||||
if (native_state.last_msg_seq_num() > er.data->msg_seq_num) {
|
||||
LOG(OrderMan, Warn,
|
||||
"Ignoring already seen execution report {} in order {}",
|
||||
|
||||
@@ -73,7 +73,7 @@ void ErrorReportServer::send_report(
|
||||
}
|
||||
|
||||
// log error for visibility
|
||||
LOG(Gen, Error, "Support request from {} - {} \n{}", request.user(), request.contact(), request.user_description());
|
||||
LOG(Gen, Error, "Support request from {} - {} {}", request.user(), request.contact(), request.user_description());
|
||||
|
||||
ErrorResponse r;
|
||||
reply(r);
|
||||
|
||||
@@ -139,7 +139,7 @@ body {
|
||||
.is-main-control-bar {
|
||||
margin:2rem 3rem 0 0;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
//text-align: center;
|
||||
.button {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ let init (context:ServiceContext.ServiceContext) =
|
||||
mdRelayExpNameData = Array.empty<ExpirationNameList>
|
||||
scratch = emptyAccount
|
||||
previousScratch = emptyAccount
|
||||
AllValue = ""
|
||||
AllValueDisplay = ""
|
||||
AllValue = "*"
|
||||
AllValueDisplay = "All"
|
||||
OptionsMenuOpen = ""
|
||||
ConfirmModal = "", (Msg.SelectTab General)
|
||||
SelectedClipLimits = []
|
||||
|
||||
@@ -69,10 +69,12 @@ let GetSymbolArray (model:Model) =
|
||||
|> Array.distinct
|
||||
|
||||
let GetFilteredSymbolArray (model:Model) (remove:string []) =
|
||||
Array.map<bts.api.Product, string> (fun a -> a.symbol ) model.mdRelayProductData
|
||||
|> Array.filter (fun elm -> (not <| Array.exists (fun e -> elm = e) remove))
|
||||
|> Array.distinct
|
||||
|
||||
let a = (Array.map<bts.api.Product, string> (fun a -> a.symbol ) model.mdRelayProductData
|
||||
|> Array.filter (fun elm -> (not <| Array.exists (fun e -> elm = e) remove))
|
||||
|> Array.distinct)
|
||||
match a.Length with
|
||||
| 0 -> a
|
||||
| _ -> [ a; [|model.AllValueDisplay|] ] |> Array.concat
|
||||
|
||||
let limitFilter (positionLimit:AccountSecurityPositionLimitUnit option) (clipLimit:AccountSecurityClipLimitUnit option) allValueDisplay (array:'a []) =
|
||||
match positionLimit, clipLimit with
|
||||
@@ -193,7 +195,11 @@ let clipLimitsView model dispatch =
|
||||
&& (elm.security.securityType = limit.security.securityType)
|
||||
)
|
||||
model.scratch.securityClipLimits.limit
|
||||
|> Array.map (fun (elm:AccountSecurityClipLimitUnit) -> elm.security.securitySymbol)
|
||||
|> Array.map (fun (elm:AccountSecurityClipLimitUnit) ->
|
||||
match (elm.security.securitySymbol = model.AllValue) with
|
||||
| true -> model.AllValueDisplay
|
||||
| false -> elm.security.securitySymbol
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -223,7 +229,7 @@ let clipLimitsView model dispatch =
|
||||
)
|
||||
TableControl.Cell (Fields.enumSelect<string>
|
||||
""
|
||||
limit.security.securitySymbol
|
||||
(match (limit.security.securitySymbol = model.AllValue) with | true -> model.AllValueDisplay | false -> limit.security.securitySymbol)
|
||||
(GetFilteredSymbolArray model <| (getUsedSymbolNames limit))
|
||||
[]
|
||||
(fun value -> ((limit, (changeSymbol limit value)) |> ClipLimits |> dispatch))
|
||||
@@ -262,7 +268,7 @@ let clipLimitsView model dispatch =
|
||||
div [ ClassName "column is-size-7 has-text-grey-mid is-help-text"; ]
|
||||
[str "Clip limits can be set for one security, for all securities in a product (by selecting \"All\" for securities), or for all products within a security type (by selecting \"All\" for both symbol and security)"]
|
||||
]
|
||||
TableControl.createDataTable (Some headerRow) rws (Button.button "Add" "" "plus" Button.Enabled addRow)
|
||||
TableControl.createDataTable (Some headerRow) rws (Button.button "Add" "" "plus-circle" Button.Enabled addRow)
|
||||
]
|
||||
|
||||
let productLimits model dispatch =
|
||||
@@ -362,7 +368,11 @@ let productLimits model dispatch =
|
||||
&& (elm.product.securityType = limit.product.securityType)
|
||||
)
|
||||
model.scratch.productPositionLimits
|
||||
|> Array.map (fun (elm:AccountProductPositionLimitUnit) -> elm.product.securitySymbol)
|
||||
|> Array.map (fun (elm:AccountProductPositionLimitUnit) ->
|
||||
match (elm.product.securitySymbol = model.AllValue) with
|
||||
| true -> model.AllValueDisplay
|
||||
| false -> elm.product.securitySymbol
|
||||
)
|
||||
|
||||
|
||||
let rws:TableControl.row list =
|
||||
@@ -390,7 +400,7 @@ let productLimits model dispatch =
|
||||
)
|
||||
TableControl.Cell (Fields.enumSelect<string>
|
||||
""
|
||||
limit.product.securitySymbol
|
||||
(match (limit.product.securitySymbol = model.AllValue) with | true -> model.AllValueDisplay | false -> limit.product.securitySymbol)
|
||||
(GetFilteredSymbolArray model (getUsedSymbolNames limit))
|
||||
[]
|
||||
(fun value -> ((limit, (changeSymbol limit value)) |> ProductLimits |> dispatch))
|
||||
@@ -433,7 +443,7 @@ let productLimits model dispatch =
|
||||
div [ ClassName "column is-size-7 has-text-grey-mid is-help-text" ]
|
||||
[str "This limits the net position for a product (i.e. accumulated position over all securities belonging to a product). This can be set for one product, or for all products within a security type by selecting \"All\" for symbol"]
|
||||
]
|
||||
TableControl.createDataTable (Some headerRow) rws (Button.button "Add" "" "plus" Button.Enabled addRow)
|
||||
TableControl.createDataTable (Some headerRow) rws (Button.button "Add" "" "plus-circle" Button.Enabled addRow)
|
||||
br []
|
||||
]
|
||||
|
||||
@@ -551,7 +561,7 @@ let securityLimitsView model dispatch =
|
||||
div [ ClassName "column is-size-7 has-text-grey-mid is-help-text" ]
|
||||
[str "Optionally, the net position for one or more securities can also be limited. For example, the Product Limits can be set to restrict the Max Net Long Position across all ES futures to 10,000 contracts, but Security Limits can be used to restrict the ES Jun future to 500 only"]
|
||||
]
|
||||
TableControl.createDataTable (Some headerRow) rws (Button.button "Add" "" "plus" Button.Enabled addRow)
|
||||
TableControl.createDataTable (Some headerRow) rws (Button.button "Add" "" "plus-circle" Button.Enabled addRow)
|
||||
]
|
||||
|
||||
let LimitsView model dispatch =
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
$known_good = @{
|
||||
"6A" = 1000
|
||||
"6B" = 625
|
||||
"6D" = 1000
|
||||
"6E" = 1250
|
||||
"6J" = 1250
|
||||
"CL" = 1000
|
||||
"EMD" = 100
|
||||
"ES" = 50
|
||||
"GC" = 100
|
||||
"GE" = 2500
|
||||
"HE" = 400
|
||||
"HG" = 5000
|
||||
"HO" = 42000
|
||||
"LE" = 400
|
||||
"M6E" = 125
|
||||
"NG" = 100
|
||||
"NQ" = 20
|
||||
"QM" = 500
|
||||
"RB" = 42000
|
||||
"RTY" = 50
|
||||
"SI" = 5000
|
||||
"VX" = 1000
|
||||
"YI" = 10
|
||||
"YM" = 5
|
||||
"ZB" = 1000
|
||||
"ZC" = 50
|
||||
"ZF" = 1000
|
||||
"ZL" = 600
|
||||
"ZM" = 100
|
||||
"ZN" = 1000
|
||||
"ZQ" = 4167
|
||||
"ZS" = 50
|
||||
"ZT" = 2000
|
||||
"ZW" = 50
|
||||
}
|
||||
@@ -1,43 +1,9 @@
|
||||
$known_good = @{
|
||||
"6A" = 1000
|
||||
"6B" = 625
|
||||
"6D" = 1000
|
||||
"6E" = 1250
|
||||
"6J" = 1250
|
||||
"M6E" = 125
|
||||
"NQ" = 20
|
||||
"ES" = 50
|
||||
"YM" = 5
|
||||
"RTY" = 50
|
||||
"VX" = 1000
|
||||
"EMD" = 100
|
||||
"GE" = 2500
|
||||
"ZB" = 1000
|
||||
"ZN" = 1000
|
||||
"ZF" = 1000
|
||||
"ZT" = 2000
|
||||
"ZQ" = 4167
|
||||
"SI" = 50
|
||||
"YI" = 10
|
||||
"GC" = 100
|
||||
"HG" = 250
|
||||
"CL" = 1000
|
||||
"NG" = 100
|
||||
"HO" = 420
|
||||
"QM" = 500
|
||||
"RB" = 420
|
||||
"ZC" = 50
|
||||
"ZS" = 50
|
||||
"ZW" = 50
|
||||
"LE" = 400
|
||||
"HE" = 400
|
||||
"ZM" = 100
|
||||
"ZL" = 600
|
||||
}
|
||||
# Dot sourcing the reqs file will make
|
||||
. .\Generate-Cpps-reqs.ps1
|
||||
|
||||
$cme_file_url = "ftp://ftp.cmegroup.com/pub/fprf/csv/cmeg.fut.prf.csv"
|
||||
$csv_save_path = ".\cmeg.fut.prf.csv"
|
||||
$output_file = ".\cpp_out.txt"
|
||||
$output_file = ".\cpp_config.conf"
|
||||
|
||||
# turn off console update progress while downloading
|
||||
$CurrentProgressPref = $ProgressPreference;
|
||||
@@ -47,11 +13,23 @@ $ProgressPreference = $CurrentProgressPref
|
||||
|
||||
$a = Import-Csv $csv_save_path
|
||||
$b = $a | select Sym, Mult | Group Sym | Sort Name
|
||||
|
||||
@"
|
||||
# Generated by automated script. DO NOT EDIT!
|
||||
# source: tools/cpp_helper/generate-cpps.ps1
|
||||
"@ | out-file -Encoding ascii $output_file
|
||||
"CppConfig`n{`n`tvalue`n`t{" | out-file -Encoding ascii $output_file -Append
|
||||
$b | % {
|
||||
if ($known_good.ContainsKey($_.Group[0].Sym)) {
|
||||
$cpp = $known_good[$_.Group[0].Sym]
|
||||
} else {
|
||||
$cpp = $_.Group[0].Mult
|
||||
}
|
||||
"cpp_entry {{ `n`tkey: `"{0}`"`n`tvalue: {1}`n}}" -f $_.Group[0].Sym, $cpp } `
|
||||
| out-file -Encoding ascii $output_file
|
||||
"`t`tcpp_entry {{ key: `"{0}`" value: {1} }}" -f $_.Group[0].Sym, $cpp } `
|
||||
| out-file -Encoding ascii $output_file -Append
|
||||
"`t}`n}" | out-file -Encoding ascii $output_file -Append
|
||||
Remove-Item $csv_save_path -Force
|
||||
|
||||
# You can now copy the output file to defaults assuming all looks good
|
||||
# Copy-Item .\cpp_config.conf ..\..\src\lib\config\defaults\ -Force
|
||||
# Remove-Item cpp-config.conf
|
||||
Reference in new issue
Block a user