Compare commits

...
16 Commits
Author SHA1 Message Date
Pedro Pinto e7806d2fec Merge branch 'master' into prod 2020-09-16 16:28:36 -04:00
Taha Afzal c30bab899f Changing TASCounter to use the new formula 2020-07-29 09:43:00 -04:00
Taha Afzal 46544f36f1 Pyramid Algo now sends hedges for fills while a cancel is in flight 2020-07-23 13:13:52 -04:00
John Colvin c7df3ac43c Merge remote-tracking branch 'origin/csv_export' into prod 2020-07-22 10:44:58 -05:00
Jake Norbut 9d1b03d1e2 fixing merge 2020-03-04 11:51:05 -06:00
Jake Norbut 9a677cf980 merging master into csv_export; taking master 2020-03-04 11:50:56 -06:00
Jake Norbut 25445d58d0 refactoring TaS CSV export 2019-12-18 13:48:14 -06:00
Jake Norbut 0b313a8aa8 refactoring Fills CSV export 2019-12-18 13:47:36 -06:00
Jake Norbut 54f954fd4b adding CSV export to order book 2019-12-18 13:47:03 -06:00
Jake Norbut 980f38ba03 using OrderedDictionary to CSVs are output with a predictable column order 2019-12-18 13:46:22 -06:00
Jake Norbut 9eada2724b Having the Command be on the Window rather than the VM; nice thinking John 2019-12-18 10:11:10 -06:00
Jake Norbut b5f53ee918 adding csv_properties to TimeAndSalesVM.Row 2019-12-18 10:10:04 -06:00
Jake Norbut efc175f654 adding export to csv command to Fills window 2019-12-17 10:32:58 -06:00
Jake Norbut 6074afd999 adding csv export command to fills vm 2019-12-17 10:32:15 -06:00
Jake Norbut 219dff4055 adding new export_csv IDialogCommand which just creates a new file 2019-12-17 10:28:57 -06:00
Jake Norbut 9a3b07b622 adding ToCsv IEnumerable ext method in a new util namespace 2019-12-17 10:21:38 -06:00
10 changed files with 212 additions and 18 deletions

No files matched your search

@@ -1,6 +1,7 @@
using bts.api;
using bts.co2;
using bts.utils;
using bts.utils.csv;
using mbts;
using mbts.spark;
using mbts.ui.controls;
@@ -49,6 +50,18 @@ namespace bts.shell.time_and_sales {
public void raise_invalidated() =>
invalidated?.Invoke();
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(TimeAndSalesRowVM),
new string[] {
nameof(security),
nameof(time),
nameof(price),
nameof(size),
nameof(aggressor_side),
nameof(security_key),
});
public bool Equals(TimeAndSalesRowVM other) =>
other != null &&
row_key == other.row_key;
@@ -2,6 +2,7 @@
using bts.shell.fills;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts;
using mbts.co2;
using mbts.persist;
@@ -124,6 +125,8 @@ namespace bts.shell.time_and_sales {
show_in_context_menu = _can_pause_updates
};
_commands = new SparkCommand[] {
// csv export
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(TimeAndSalesRowVM.csv_ordered_properties), "TaS")),
// ladder
SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder)
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
@@ -5,6 +5,7 @@ using bts.collections;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using bts.utils.csv;
using mbts.collections;
using mbts.persist;
using mbts.spark.filters;
@@ -333,6 +334,27 @@ namespace mbts.spark {
public override bool Equals(object obj) => Equals(obj as Row);
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(typeof(Row),
new string[] {
nameof(security),
nameof(order_type),
nameof(root_algo_display_name),
nameof(side),
nameof(fill_time),
nameof(user),
nameof(account),
nameof(exchange),
nameof(time_in_force),
nameof(size),
nameof(price),
nameof(fill_kind),
nameof(fill_status),
nameof(fill_key),
nameof(order_key),
nameof(security_key)
});
public event Action invalidated;
void IRowVM.raise_invalidated() => invalidated?.Invoke();
}
@@ -4,6 +4,7 @@ using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
@@ -61,6 +62,7 @@ namespace mbts.spark {
_view = new FillsView { DataContext = _vm };
_commands = new List<SparkCommand> {
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(FillsVM.Row.csv_ordered_properties), "Fills")),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
@@ -4,6 +4,7 @@ using bts.api.orderman;
using bts.collections;
using bts.spark;
using bts.utils;
using bts.utils.csv;
using mbts.ui;
using mbts.ui.controls;
using System;
@@ -11,6 +12,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Windows.Input;
using System.Windows.Media;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
@@ -60,6 +62,35 @@ namespace mbts.spark {
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
public event PropertyChangedEventHandler PropertyChanged;
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(OrderBookRowVM),
new string[] {
nameof(security_text),
nameof(symbol),
nameof(time_created),
nameof(last_change),
nameof(user),
nameof(account),
nameof(exchange),
nameof(time_in_force),
nameof(order_type),
nameof(root_algo_display_name),
nameof(order_sub_type),
nameof(side),
nameof(quantity),
nameof(filled_quantity),
nameof(price),
nameof(avg_price),
nameof(last_price),
nameof(piq),
nameof(status),
// last message (could contains commas, so skipping)
nameof(security_key),
nameof(order_key),
nameof(parent_order_key),
});
// Core
public OrderSecurityRootPair order_tuple { get; private set; }
public OrderRecordImmutable order => order_tuple.order.order;
@@ -6,6 +6,7 @@ using bts.shell.fills;
using bts.shell.orderbook;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts.persist;
using mbts.spark.utilities;
using mbts.ui;
@@ -231,6 +232,7 @@ namespace mbts.spark {
toggle_collapse_all_command,
toggle_expand_all_command,
show_grid_settings_command,
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(OrderBookRowVM.csv_ordered_properties), "OrderBook")),
// hidden cmds (i.e., keyboard shortcuts at window level)
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.ModifyOrders, null, _vm.modify_selected_orders_cmd, OrderBookView.KeyboardShortcuts.ModifyOrders)),
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders)),
@@ -64,6 +64,25 @@ namespace mbts.ui {
}
}, can_execute == null ? null : new Func<object, bool>(_ => can_execute()));
public static IDialogCommand export_csv(
Func<string> get_file_content,
string source_window_name) =>
new AnonymousFileDialogCommand<object>(_ =>
_create_save_file_dialog(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
$"BTS_Spark_{source_window_name}_{DateTime.Now.ToString("yyyyMMdd_HHmm")}.csv",
new[] { ("CSV (comma delimited)", new string[] { "csv" }) }
),
(_, file) => {
try {
File.WriteAllText(file, get_file_content.Invoke());
} catch (Exception ex) {
BTSMessageBox.Show($"Error exporting data to csv: {ex}.", "CSV Export Error", BTSMessageBoxButtons.OK, System.Windows.MessageBoxImage.Error, true, false, true);
}
},
// we are always able to perform an export, even when no rows (it will just be a header)
(_) => true);
static TDialog _create_file_dialog<TDialog>(string initial_dir = null, IReadOnlyList<(string name, string[] extensions)> filter_items = null) where TDialog : FileDialog, new() {
var rv = new TDialog();
if (!string.IsNullOrEmpty(initial_dir))
+52
View File
@@ -0,0 +1,52 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
namespace bts.utils.csv {
public static class CsvHelper {
public static string ToCsv<T>(this IEnumerable<T> items, OrderedDictionary properties = null) where T : class {
var output = "";
var delimiter = ",";
if (properties == null) {
properties = new OrderedDictionary();
foreach (var pi in typeof(T).GetProperties()) {
properties.Add(pi.Name, pi);
}
}
using (var sw = new StringWriter()) {
var dict_values = properties.Values.OfType<PropertyInfo>();
var header = dict_values.Select(n => n.Name).Aggregate((first, second) => first + delimiter + second);
sw.WriteLine(header);
foreach (var i in items) {
var row = dict_values.Select(x => x.GetValue(i, null))
.Select(x => x == null ? "null" : x.ToString())
.Aggregate((first, second) => first + delimiter + second);
sw.WriteLine(row);
}
output = sw.ToString();
}
return output;
}
public static OrderedDictionary create_ordering_maintainer(Type t, string[] property_names_in_order) {
OrderedDictionary od = new OrderedDictionary();
var props = t.GetProperties();
foreach (var n in property_names_in_order) {
var query = props.Where(pi => pi.Name == n);
if (query.Any()) {
var property_info = query.First();
if (!od.Contains(property_info)) {
od.Add(n, property_info);
}
}
}
return od;
}
}
}
@@ -16,7 +16,7 @@ namespace orderman {
using namespace api::orderman;
enum class PyramidAlgo::Status { WAITING_FOR_MD, WAITING_FOR_MD_ATTACH, WORKING, DONE };
enum class PyramidAlgo::Status { WAITING_FOR_MD, WAITING_FOR_MD_ATTACH, WORKING, WAITING_FOR_CXL_ACK, DONE };
enum class PyramidAlgo::ExitPath { CANCEL, ERR, COMPLETE };
struct LegData{
@@ -192,6 +192,7 @@ void PyramidAlgo::do_update(const api::orderman::Update& update) {
"Order cancelled (Cover prices not met)");
break;
}
case PyramidAlgo::Status::WAITING_FOR_CXL_ACK:
case PyramidAlgo::Status::DONE:
break;
}
@@ -215,6 +216,7 @@ IAlgoOrder::OnCancelResponse PyramidAlgo::on_cancel(){
case PyramidAlgo::Status::WORKING:
_cxl_and_end(PyramidAlgo::ExitPath::CANCEL,"Algo cancelled");
break;
case (PyramidAlgo::Status::WAITING_FOR_CXL_ACK):
case PyramidAlgo::Status::DONE:
break;
}
@@ -242,6 +244,7 @@ void PyramidAlgo::on_child_change(OrderKey key, const ChildState &st) {
_check_for_completion();
break;
case PyramidAlgo::Status::WORKING:
case (PyramidAlgo::Status::WAITING_FOR_CXL_ACK):
leg->order_state = st;
_algo().update_fill_state(st.total_fills, st.avg_price);
_send_hedges(st.last_fills);
@@ -388,6 +391,7 @@ void PyramidAlgo::_analyze_md_and_act(){
_cxl_and_end(PyramidAlgo::ExitPath::CANCEL,
"Order cancelled (Cover prices not met)");
break;
case PyramidAlgo::Status::WAITING_FOR_CXL_ACK:
case PyramidAlgo::Status::DONE:
break;
}
@@ -405,14 +409,19 @@ void PyramidAlgo::_switch_state(Status new_state){
bts_assert(me->state == PyramidAlgo::Status::WAITING_FOR_MD ||
me->state == PyramidAlgo::Status::WAITING_FOR_MD_ATTACH);
break;
case (PyramidAlgo::Status::WAITING_FOR_CXL_ACK):
break;
case (PyramidAlgo::Status::DONE):
bts_assert(
// when an order is entered and the criteria is not met
me->state == PyramidAlgo::Status::WAITING_FOR_MD ||
// when an order is attached and the market conditions are not met
me->state == PyramidAlgo::Status::WAITING_FOR_MD_ATTACH ||
// when the child order gets into an unexpected state
me->state == PyramidAlgo::Status::WORKING);
// when the child order enters a terminal state, while the algo
// is in a working state (external cxl, target reached)
me->state == PyramidAlgo::Status::WORKING ||
// the usual controlled cxl, ie working->waiting_for_cxl_ack->done
me->state == PyramidAlgo::Status::WAITING_FOR_CXL_ACK);
break;
}
LOG(OrderMan, Info,"PyramidAlgo::_switch_state key:{} {}->{} ",
@@ -494,8 +503,8 @@ void PyramidAlgo::_check_for_completion(){
break;
case (ChildStatus::CANCELED):
_cxl_and_end(PyramidAlgo::ExitPath::ERR,
"Top spread unexpectedly cancelled");
break;
"Top spread cancelled");
break;
}
}
@@ -507,8 +516,11 @@ void PyramidAlgo::_cxl_and_end(PyramidAlgo::ExitPath exit_path,
if(top_ord_state.status == ChildStatus::WORKING){
if(detach_top)
_algo().detach_child(me->legs[0].order_key);
else
else{
_algo().cancel_child(me->legs[0].order_key);
_switch_state(PyramidAlgo::Status::WAITING_FOR_CXL_ACK);
return;
}
}
}
switch(exit_path){
@@ -524,6 +536,7 @@ void PyramidAlgo::_cxl_and_end(PyramidAlgo::ExitPath exit_path,
}
_switch_state(PyramidAlgo::Status::DONE);
}
// true the out qty is changed it be below
// the filled qty
bool PyramidAlgo::_target_reached() const{
@@ -26,17 +26,21 @@ enum class TASCounterAlgo::Status { WORKING, DONE };
// (the vwap of the TAS contracts is not used, but for uniformity is kept in the
// the same structure)
struct ContractMarketData{
private:
uint32_t tot_vol = 0;
double running_sum = 0;
public:
Book book;
double estimated_settle = 0.0;
void on_trade (const TickerUpdateEvent::Trade& t){
tot_vol += t.quantity.value();
running_sum += t.price.value()*t.quantity.value();
}
const double vwap(){
double vwap() const{
return tot_vol == 0 ? os::nan() : running_sum/tot_vol;
}
uint32_t traded_qty() const {return tot_vol;}
double traded_value() const {return running_sum;}
};
// the input for the t1 front, second and third orders, as well as the template
// and working order key are kept here
@@ -100,6 +104,47 @@ bool trade_within_time_win (const Timestamp&, const Timestamp&,
const TickerUpdateEvent::Trade& t);
bool now_within_time_win (const Timestamp&, const Timestamp&);
double compute_t1_front_settle (const AlgoData& d){
double rv = d.t1_front.vwap();
rv = round_to_nearest(rv,d.tick_size);
return rv;
}
double compute_t1_second_settle (const AlgoData& d){
double p_t1_front = compute_t1_front_settle(d);
double part_1 = d.t1_second.traded_value();
double part_2 = d.t2_front.traded_value() -
(d.t2_front.traded_qty() * p_t1_front);
uint32_t traded_qty_sum = d.t2_front.traded_qty() +
d.t1_second.traded_qty();
double rv = traded_qty_sum == 0 ? os::nan() :
(part_1+part_2)/traded_qty_sum;
LOG(OrderMan, Diag, "TASCounterAlgo::compute_t1_second_settle "
"p_t1_front:{} part_1:{} part_2:{} traded_qty_sum:{} rv:{}",
p_t1_front, part_1, part_2, traded_qty_sum, rv);
return round_to_nearest(rv,d.tick_size);
}
double compute_t1_third_settle (const AlgoData& d){
double p_t1_front = compute_t1_front_settle(d);
double p_t1_second = compute_t1_second_settle(d);
double part_1 = d.t1_third.traded_value();
double part_2 = d.t2_second.traded_value() -
(d.t2_second.traded_qty()*p_t1_second);
double part_3 = d.t3_front.traded_value() -
(d.t3_front.traded_qty()*p_t1_front) -
(d.t3_front.traded_qty()*p_t1_second);
double traded_qty_sum = d.t3_front.traded_qty() +
d.t2_second.traded_qty() +
d.t1_third.traded_qty();
double rv = traded_qty_sum == 0 ? os::nan() :
(part_1+part_2+part_3)/traded_qty_sum;
LOG(OrderMan, Diag, "TASCounterAlgo::compute_t1_third_settle "
"p_t1_front:{} p_t1_second:{} part_1:{} part_2:{} part_3:{} "
"traded_qty_sum:{} rv:{}",
p_t1_front, p_t1_second, part_1, part_2, part_3, traded_qty_sum, rv);
return round_to_nearest(rv,d.tick_size);
}
TASCounterAlgo::TASCounterAlgo() : me(new PImpl()) {}
TASCounterAlgo::~TASCounterAlgo() {}
@@ -214,17 +259,9 @@ void TASCounterAlgo::_cxl_and_end (bool is_error, const std::string& err_msg){
void TASCounterAlgo::_compute_settles(){
AlgoData& d = me->data;
d.t1_front.estimated_settle = d.t1_front.vwap();
d.t1_second.estimated_settle = d.t2_front.vwap() - d.t1_front.vwap();
d.t1_third.estimated_settle = d.t3_front.vwap() - d.t1_front.vwap() -
d.t1_second.vwap();
// round to closest value
d.t1_front.estimated_settle = round_to_nearest(
d.t1_front.estimated_settle,me->data.tick_size);
d.t1_second.estimated_settle = round_to_nearest(
d.t1_second.estimated_settle,me->data.tick_size);
d.t1_third.estimated_settle = round_to_nearest(
d.t1_third.estimated_settle,me->data.tick_size);
d.t1_front.estimated_settle = compute_t1_front_settle(d);
d.t1_second.estimated_settle = compute_t1_second_settle(d);
d.t1_third.estimated_settle = compute_t1_third_settle(d);
}
// the decision to send an order can be caused by