Compare commits

..
11 Commits
18 changed files with 1624 additions and 5583 deletions

No files matched your search

+10
View File
@@ -1,3 +1,13 @@
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;
}
}
}
@@ -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> {
@@ -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
}
}
}
}
}
File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -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);
+1 -1
View File
@@ -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
+7
View File
@@ -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 {
@@ -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);
}
}
@@ -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;
@@ -349,11 +350,23 @@ 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::PRICE& price, int size, AggressorSide 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);
data->handle.add_trade(ts, ps, date, agside, data->electronic_volume + size);
}
void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& arg) {
@@ -364,7 +377,7 @@ void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const on
arg.entry().entrySize(size);
onixs::PRICE price;
arg.entry().entryPx(price);
on_trade(data, price, size, arg.message().transactTime());
on_trade(data, price, size, AGGRESSOR_SIDE_NONE, arg.message().transactTime());
}
}
void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const onixs::TradeSummaryArgs& arg) {
@@ -373,7 +386,9 @@ void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const on
if (data != NULL) {
auto size = arg.entry().entrySize();
onixs::PRICE price = arg.entry().entryPx();
on_trade(data, price, size, arg.message().transactTime());
onixs::AggressorSide::Enum agside = onixs::AggressorSide::NoAggressor;
arg.entry().aggressorSide(agside);
on_trade(data, price, size, map_aggressor(agside), arg.message().transactTime());
}
}
void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const onixs::TradeArgs& arg) {
@@ -383,7 +398,9 @@ void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const on
if (data != nullptr) {
int size = arg.entry().entrySize();
onixs::PRICE price = arg.entry().entryPx();
on_trade(data, price, size, arg.message().transactTime());
onixs::AggressorSide::Enum agside = onixs::AggressorSide::NoAggressor;
arg.entry().aggressorSide(agside);
on_trade(data, price, size, map_aggressor(agside), arg.message().transactTime());
}
}
}
@@ -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());
@@ -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");
+36
View File
@@ -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
}
+17 -39
View File
@@ -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