Compare commits

..
54 changed files with 1049 additions and 1595 deletions

No files matched your search

+1
View File
@@ -18,6 +18,7 @@ src/client/spark/Properties/launchSettings.json
compile_commands.json
.cquery_cached_index/
src/client/sparkcmd/Properties/launchSettings.json
src/sdk/examples/client_sdk/bts.api.csharp/src/Properties/launchSettings.json
node_modules
paket-files
project.lock.json
@@ -143,7 +143,7 @@ namespace mbts.spark {
string candidate = string.Empty;
if (a.HasValue) {
var algo_name = a.Value.name;
var words = algo_name.Split(delim, StringSplitOptions.RemoveEmptyEntries);
var words = algo_name.Split(delim);
if (words.Length == 1) {
var single_word = words[0];
if (single_word.Length > 1) {
@@ -152,7 +152,7 @@ namespace mbts.spark {
candidate = single_word;
}
} else {
candidate = string.Join(string.Empty, words.Take(2).Select(w => w.Substring(0, 1)));
candidate = words.Take(2).Select(w => w.Substring(0, 1)).list_string(string.Empty);
}
}
return candidate.ToUpper();
-1
View File
@@ -69,5 +69,4 @@ message DynamicAlgos{
optional bool enable_tas = 3 [default = false];
optional bool enable_tascounter = 4 [default = false];
optional bool enable_limit_cover = 5 [default = false];
optional bool enable_onsignal = 6 [default = false];
}
-3
View File
@@ -211,9 +211,6 @@ inline bool gr_than(double p1, double p2, double tick_size) {
return gr_than_eq(p1, p2, tick_size) &&
!test_price_equality(p1, p2, tick_size);
}
inline bool test_price_equality(double price_1, double price_2) {
return std::fabs(price_2 - price_1) < market_epsilon();
}
} // namespace securities
} // namespace bts
@@ -1,247 +0,0 @@
#include "lib/orderman/orders/algo/algo_orders/OnSignalAlgo.h"
#include "lib/domain_model/Security.h"
#include "lib/orderman/order_factory/IOrderFactory.h"
#include "lib/orderman/orderman_utils/Book.h"
#include "lib/orderman/orderman_utils/orderman_utils.h"
#include "lib/orderman/orders/algo/IAlgoServices.h"
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
#include "lib/orderman/actors/SODPosition.h"
#include "lib/os/os.h"
#include "lib/domain_model/Security.h"
#include "lib/utils/PBUtils.h"
#include "lib/utils/datetime.h"
#include <google/protobuf/util/time_util.h>
#include <boost/tokenizer.hpp>
#include <stdint.h>
namespace bts {
namespace orderman {
using namespace api::orderman;
using namespace google::protobuf;
using namespace boost;
struct OnSignalLegData{
const SecurityKey sec_key;
ChildTemplate* order_template = nullptr;
const Price price;
const Quantity quantity;
const api::Side side;
OrderKey order_key;
ChildState order_state;
};
enum class OnSignalAlgo::State { WAITING_FOR_SIGNAL, WORKING, DONE };
struct OnSignalAlgoParams{
std::optional<SecurityKey> signal_key_1 = std::nullopt;
std::optional<google::protobuf::Timestamp> time = std::nullopt;
bool preopen = false;
bool open = false;
std::vector<OnSignalLegData> legs;
OnSignalLegData* find_leg_from_ord_key (OrderKey ord_key){
for (auto& l : legs)
if(l.order_key == ord_key)
return &l;
return nullptr;
}
};
struct OnSignalAlgo::PImpl {
IAlgoServices* algo_services = nullptr;
State state = State::WAITING_FOR_SIGNAL;
OnSignalAlgoParams algo_params;
PImpl(){
}
};
ChildTemplate* _get_template (IBeforeLaunchAlgoServices& serv,
const CustomOrderDefinition& cod,
SecurityKey key,
api::Side side){
OrderDefinition rv = serv.definition();
rv.set_security_key(key.value());
rv.set_side(side);
rv.mutable_custom_def()->CopyFrom(cod);
return &(serv.make_child_template(rv));
}
bool _convert_params (IBeforeLaunchAlgoServices& serv,
const CustomOrderDefinition& cod,
OnSignalAlgoParams& out_p){
const DynamicOrderDefinition& def = cod.dynamic();
const auto& params = def.order_params();
auto hh = get_if<uint32_t>(params, "Time Hours");
auto mm = get_if<uint32_t>(params, "Time Minutes");
auto ss = get_if<uint32_t>(params, "Time Seconds");
if(hh.has_value() && hh.value() <= 23 &&
mm.has_value() && mm.value() <= 59 &&
ss.has_value() && ss.value() <= 59)
out_p.time = SODPosition::today_local_time_to_utc_timestamp (
hh.value(),mm.value(),ss.value(),true,false);
auto preopen = get_if<bool>(params, "Preopen");
out_p.preopen = preopen.has_value() ? preopen.value() : false;
auto open = get_if<bool>(params, "Open");
out_p.open = open.has_value() ? open.value() : false;
auto k1 = get_if<uint32_t>(params, "Signal Contract 1");
if(k1.has_value())
out_p.signal_key_1 = SecurityKey(k1.value());
for (int i=0;;i++){
auto idx = std::to_string(i+1);
auto contract = get_if<uint32_t>(params, "Order " + idx + " Contract");
auto custom_ord = get_if<CustomOrderDefinition>(params, "Order " + idx);
auto price = get_if<double>(params, "Order " + idx + " Price");
auto qty = get_if<int32_t>(params, "Order " + idx + " Quantity");
if(contract.has_value() && custom_ord.has_value() && price.has_value() &&
qty.has_value() && qty.value() != 0){
auto side = qty > 0 ? api::Side::SIDE_BUY : api::Side::SIDE_SELL;
uint32_t eff_qty = std::abs(qty.value());
auto key = SecurityKey(contract.value());
ChildTemplate* child_template = _get_template(serv,custom_ord.value(),key,side);
OnSignalLegData leg =
{
key,
child_template,
Price(price.value()),
Quantity(eff_qty),
side,
OrderKey(0),
ChildState()
};
out_p.legs.push_back(leg);
}
else
break;
}
bool valid = out_p.legs.size() > 0 &&
(out_p.time.has_value() ||
(out_p.signal_key_1.has_value() && (out_p.preopen || out_p.open)));
return valid;
}
OnSignalAlgo::OnSignalAlgo() : me (new PImpl()){}
OnSignalAlgo::~OnSignalAlgo() {}
OrderOpResult OnSignalAlgo::do_before_launch(IBeforeLaunchAlgoServices& serv){
auto& def = serv.definition();
if (def.has_custom_def() &&
def.custom_def().has_dynamic() &&
_convert_params(serv,def.custom_def(),me->algo_params))
return {};
return {OrderError::ORDER_ERROR_INVALID_PARAMETER,
std::string("Parameters are not well formed")};
}
void OnSignalAlgo::do_launch(IAlgoServices& algo_services){
me->algo_services = &algo_services;
me->algo_services->subscribe_timer();
if(me->algo_params.signal_key_1.has_value())
me->algo_services->subscribe_status(me->algo_params.signal_key_1.value());
}
void OnSignalAlgo::do_launch_attached(IAlgoServices& algo_services,
OrderKey,
ChildState){
me->algo_services = &algo_services;
_cxl_and_end(true, "OnSignal Algo can not be attached to working orders");
}
void OnSignalAlgo::do_update(api::orderman::Update const &){
LOG(OrderMan, Warn, "OnSignalAlgo::do_update order:{} state:{} update not allowed, please cxl and re-enter ",
me->algo_services->order_key().value(),
static_cast<int>(me->state));
}
void OnSignalAlgo::on_child_change(OrderKey key, const ChildState& st){
LOG(OrderMan, Info,
"OnSignalAlgo::on_child_change key:{} child_key:{} state:{} status:{} "
"total_fills:{} last_price:{}",
me->algo_services->order_key(),
key,
static_cast<int>(me->state),
static_cast<int>(st.status),
st.total_fills,
st.last_price);
switch (me->state){
case (OnSignalAlgo::State::WAITING_FOR_SIGNAL):
LOG(OrderMan, Error,
"OnSignalAlgo::on_child_change key:{} in WAITING_FOR_SIGNAL",
me->algo_services->order_key());
break;
case (OnSignalAlgo::State::WORKING):
{
auto ld_ptr = me->algo_params.find_leg_from_ord_key(key);
if (ld_ptr != nullptr)
ld_ptr->order_state = st;
}
for (const auto& l : me->algo_params.legs)
if(l.order_state.status == ChildStatus::WORKING)
return;
_cxl_and_end(false,"");
break;
case (OnSignalAlgo::State::DONE):
break;
}
}
void OnSignalAlgo::on_status(SecurityKey key, co2::TradingStatus s){
if(me->algo_params.signal_key_1.has_value() && me->algo_params.signal_key_1 == key)
switch (me->state){
case (OnSignalAlgo::State::WAITING_FOR_SIGNAL):
if(me->algo_params.preopen && s == co2::TradingStatus::TRADINGSTATUS_PRE_OPEN)
_fire_orders();
else if(me->algo_params.open && s == co2::TradingStatus::TRADINGSTATUS_NORMAL)
_fire_orders();
break;
case (OnSignalAlgo::State::WORKING):
case (OnSignalAlgo::State::DONE):
break;
}
}
void OnSignalAlgo::on_timer(){
if(me->algo_params.time.has_value())
switch (me->state){
case (OnSignalAlgo::State::WAITING_FOR_SIGNAL):
{
auto thresh = me->algo_params.time.value();
auto now = util::TimeUtil::GetCurrentTime();
auto diff = util::TimeUtil::TimestampToMicroseconds(now) -
util::TimeUtil::TimestampToMicroseconds(thresh);
if(diff >= 0)
_fire_orders();
break;
}
case (OnSignalAlgo::State::WORKING):
case (OnSignalAlgo::State::DONE):
break;
}
}
void OnSignalAlgo::_fire_orders(){
for (auto& l : me->algo_params.legs)
l.order_key = me->algo_services->launch_child(*l.order_template,l.price,l.quantity);
_switch_state(State::WORKING);
}
void OnSignalAlgo::_cxl_and_end (bool is_error, const std::string& err_msg){
if(is_error)
me->algo_services->terminate_error(err_msg);
else
me->algo_services->complete();
_switch_state(OnSignalAlgo::State::DONE);
}
void OnSignalAlgo::_switch_state (State new_state){
switch (new_state){
case (OnSignalAlgo::State::WAITING_FOR_SIGNAL):
bts_assert(false);
break;
case (OnSignalAlgo::State::WORKING):
bts_assert(me->state == OnSignalAlgo::State::WAITING_FOR_SIGNAL);
break;
case (OnSignalAlgo::State::DONE):
bts_assert(me->state != OnSignalAlgo::State::DONE);
break;
}
LOG(OrderMan, Info,"OnSignalAlgo::_switch_state key:{} {}->{} ",
me->algo_services->order_key(),
static_cast<int>(me->state),
static_cast<int>(new_state));
me->state = new_state;
}
} // namespace orderman
} // namespace bts
@@ -1,36 +0,0 @@
#pragma once
#include "lib/orderman/orders/algo/IAlgoOrder.h"
namespace bts {
namespace orderman {
class ChildTemplate;
struct LegData;
class OnSignalAlgo : public IAlgoOrder {
public:
OnSignalAlgo();
~OnSignalAlgo();
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
void do_launch(IAlgoServices &) override;
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
void do_update(api::orderman::Update const &) override;
void on_child_change(OrderKey, ChildState const &) override;
void on_status(SecurityKey, co2::TradingStatus) override;
void on_timer() override;
private:
enum class State;
struct PImpl;
std::unique_ptr<PImpl> me;
private:
IAlgoServices& _algo() const ;
// acts on md, cognizant of the state
void _switch_state (State new_state);
void _fire_orders();
void _cxl_and_end (bool is_error, const std::string& err_msg);
};
} // namespace orderman
} // namespace bts
@@ -538,14 +538,6 @@ bool PyramidAlgo::_md_recvd() const{
return true;
}
bool gr_than_eq(double p1, double p2){
return securities::test_price_equality(p1,p2) || p1 > p2;
}
bool less_than_eq(double p1, double p2){
return securities::test_price_equality(p1,p2) || p1 < p2;
}
bool PyramidAlgo::_working_state_met() const{
double cover_sum = 0.0;
size_t i = 0;
@@ -553,17 +545,10 @@ bool PyramidAlgo::_working_state_met() const{
if (i++ > 0)
cover_sum += l.opp_md()[0].price();
bool rv = !os::is_nan(cover_sum) &&
(
(
me->side == api::Side::SIDE_BUY &&
less_than_eq(me->out_price.value(),cover_sum)
)
||
(
me->side == api::Side::SIDE_SELL &&
gr_than_eq(me->out_price.value(),cover_sum)
)
);
((me->side == api::Side::SIDE_BUY &&
me->out_price.value() <= cover_sum) ||
(me->side == api::Side::SIDE_SELL &&
me->out_price.value() >= cover_sum));
if (!rv && !os::is_nan(cover_sum)){
// price condition is not met, but if the
// cum qty at the top leg price is > threashold
@@ -11,7 +11,6 @@
#include "PyramidAlgo.h"
#include "TASAlgo.h"
#include "TASCounterAlgo.h"
#include "OnSignalAlgo.h"
using namespace bts;
using namespace api::orderman;
@@ -201,7 +200,7 @@ extern "C" void init_tas_plugin(bts::SparkServices& srv) {
auto idx = std::to_string(i+1);
*tas_desc.add_param_desc() = param_double("Order " + idx + " Price");
*tas_desc.add_param_desc() = param_uint32("Order " + idx + " Quantity");
*tas_desc.add_param_desc() = param_bool("Order " + idx + " Buy?");
*tas_desc.add_param_desc() = param_bool("Order " + idx + " Buy?");
}
srv.register_dynamic_algo_order(tas_desc, []() { return std::make_unique<orderman::TASAlgo>(); });
}
@@ -213,27 +212,6 @@ extern "C" void init_limit_cover(bts::SparkServices & srv) {
srv.register_dynamic_algo_order(lc_desc, []() { return std::make_unique<orderman::LimitCoverAlgo>(); });
}
extern "C" void init_on_signal_plugin(bts::SparkServices& srv) {
DynamicOrderDescriptor on_signal;
on_signal.set_dynamic_order_type("OnSignal");
*on_signal.add_param_desc() = param_uint32("Time Hours");
*on_signal.add_param_desc() = param_uint32("Time Minutes");
*on_signal.add_param_desc() = param_uint32("Time Seconds");
*on_signal.add_param_desc() = param_bool("Preopen");
*on_signal.add_param_desc() = param_bool("Open");
// contracts who's signals have to be received to send the algo
*on_signal.add_param_desc() = param_security_key("Signal Contract 1");
size_t n_child_ords = 10;
for (size_t i=0; i< n_child_ords; i++){
auto idx = std::to_string(i+1);
*on_signal.add_param_desc() = param_security_key("Order " + idx + " Contract");
*on_signal.add_param_desc() = param_order("Order " + idx);
*on_signal.add_param_desc() = param_double("Order " + idx + " Price");
*on_signal.add_param_desc() = param_int32("Order " + idx + " Quantity");
}
srv.register_dynamic_algo_order(on_signal, []() { return std::make_unique<orderman::OnSignalAlgo>(); });
}
extern "C" void init_spark_plugin(bts::SparkServices &srv) {
auto& dyn_algo_config = config::get<config::toggles::DynamicAlgos>();
if(dyn_algo_config.enable_dorman())
@@ -246,8 +224,6 @@ extern "C" void init_spark_plugin(bts::SparkServices &srv) {
init_tascounter_plugin(srv);
if (dyn_algo_config.enable_limit_cover())
init_limit_cover(srv);
if (dyn_algo_config.enable_onsignal())
init_on_signal_plugin(srv);
}
}}}
@@ -1,15 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>netcoreapp2.1</TargetFrameworks>
<OutputType>Exe</OutputType>
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="spark_sdk" Version="2.15.10" />
<PackageReference Include="System.Reactive" Version="4.4.1" />
<PackageReference Include="System.Reactive.Core" Version="4.4.1" />
<PackageReference Include="System.Reactive.Interfaces" Version="4.4.1" />
<PackageReference Include="System.Reactive.Linq" Version="4.4.1" />
</ItemGroup>
</Project>
@@ -0,0 +1,68 @@
using bts.api.orderman;
using CommandLine;
namespace bts.api.csharp {
[Verb("watch", HelpText = "List items in the system.")]
class WatchOptions {
[Option("accounts", Required = false, SetName = "Accounts", HelpText = "print accounts in the system.")]
public bool accounts { get; set; }
[Option("securities", Required = false, SetName = "Securities", HelpText = "print securities in the system (may be combined with --filter-symbols, or --filter-securities).")]
public bool securities { get; set; }
[Option("symbols", Required = false, SetName = "Symbols", HelpText = "print all symbols in the system (may not be combined with other options).")]
public bool symbols { get; set; }
[Option("bbo", Required = false, SetName = "Books", HelpText = "print bbo for an optionally filtered set of securities/symbols.")]
public bool bbo { get; set; }
[Option("orders", Required = false, SetName = "Orders", HelpText = "print orders in the system.")]
public bool orders { get; set; }
[Option("positions", Required = false, SetName = "Positions", HelpText = "print positions in the system (may be combined with --filter-symbols, --filter-securities, and --filter-accounts).")]
public bool positions { get; set; }
// auxiliary filters
[Option("filter-securities", Required = false, HelpText = "filter securities with a regular expression (may be combined with --securities, and --bbo).")]
public string filter_securities { get; set; }
[Option("filter-symbols", Required = false, HelpText = "filter securities for a particular set of symbols (may be combined with --securities, and --bbo). Do not confuse with the --symbols option, which prints all symbols in the system.")]
public string filter_symbols { get; set; }
[Option("filter-accounts", Required = false, HelpText = "filter for a particular set of accounts.")]
public string filter_accounts { get; set; }
}
[Verb("send-order", HelpText = "Send orders.")]
class SendOrdersOptions {
// this option only applies to Iceberg orders
[Option("show-quantity", Required = false, SetName = "Iceberg")]
public double show_quantity { get; set; } = 5;
[Option("price", Required = false, HelpText = "specify the order price.")]
public double price { get; set; } = 100;
[Option("quantity", Required = false, HelpText = "specify the order quantity.")]
public uint quantity { get; set; } = 1;
[Option("account", Required = false, HelpText = "specify the account with which the order should be sent.")]
public string account { get; set; }
[Option("order-type", Required = false, HelpText = "specify the order type (limit, market, or iceberg).")]
public CustomOrderDefinition.CustomOneofCase order_type { get; private set; } = CustomOrderDefinition.CustomOneofCase.Limit;
/// Run the program with the argument <see cref="WatchOptions.securities"/> to display securities/security keys:
/// - display all securities:
/// bts.api.csharp.exe watch securities
/// - display all 'ES' securities:
/// bts.api.csharp.exe watch securities --symbols "ES"
/// - display all 'ES' and 'GC' securities:
/// bts.api.csharp.exe watch securities --symbols "ES,GC"
/// If no security key is specified, a random security is used.
[Option("security", Required = false, HelpText = "specify the order security by name, or id.")]
public string security { get; set; }
[Option("side", Required = false, HelpText = "specify the order side (buy/sell).")]
public Side side { get; set; } = Side.Buy;
}
}
@@ -1,74 +1,88 @@
using bts.api.core;
using bts.api.csharp.examples;
using bts.api.admin;
using bts.api.core;
using bts.api.csharp.data;
using bts.api.csharp.utils;
using bts.api.csharp.watch;
using bts.api.orderman;
using bts.api.position;
using CommandLine;
using System;
using System.Collections.Generic;
using System.Threading;
namespace bts.api.csharp {
class Program {
static void Main(string[] args) =>
Parser.Default.ParseArguments<Options>(args).WithParsed(_run);
static void _run(Options options) {
var exit_event = new ManualResetEvent(false);
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
var service_context = new ServiceContext();
var login = service_context.login("bts", "");
login.Wait();
var examples = new List<IDisposable>();
if (options.subscribe_securities ||! options.any_enabled) {
examples.Add(new SecurityExample(service_context.locate<MarketDataService>()));
}
if (options.subscribe_positions || !options.any_enabled) {
examples.Add(new PositionExample(service_context.locate<PositionService>()));
}
if (options.subscribe_orders || !options.any_enabled) {
examples.Add(new OrderExample(service_context.locate<OrderService>()));
}
if (options.subscribe_book || !options.any_enabled) {
examples.Add(new BookExample(service_context.locate<MarketDataService>()));
}
if (options.send_orders || !options.any_enabled) {
examples.Add(SendOrderLoop.init_loop(service_context.locate<OrderService>(), service_context.locate<MarketDataService>()));
}
Console.CancelKeyPress += on_cancel_key_press;
exit_event.WaitOne();
foreach (var ex in examples)
ex.Dispose();
//
void on_cancel_key_press(object sender, ConsoleCancelEventArgs e) {
exit_event.Set();
e.Cancel = true;
}
static void Main(string[] args) {
new Parser(cfg => cfg.CaseInsensitiveEnumValues = true)
.ParseArguments<WatchOptions, SendOrdersOptions>(args)
.MapResult(
_execution_shell<WatchOptions>(_watch),
_execution_shell<SendOrdersOptions>(_send_orders, true),
errors => 1);
}
class Options {
[Option("subscribe-securities", Required = false, HelpText = "run security-subscription example.")]
public bool subscribe_securities { get; set; }
static Func<T, int> _execution_shell<T>(Func<ServiceContext, T, IDisposable> execute, bool auto_quit = false) {
return opts => {
var exit_event = new ManualResetEvent(auto_quit);
Console.CancelKeyPress += on_cancel_key_press;
var service_context = new ServiceContext();
service_context.login("bts", "").Wait();
execute(service_context, opts);
exit_event.WaitOne();
return 0;
//
void on_cancel_key_press(object sender, ConsoleCancelEventArgs e) {
exit_event.Set();
e.Cancel = true;
}
};
}
[Option("subscribe-orders", Required = false, HelpText = "run order-subscription example.")]
public bool subscribe_orders { get; set; }
static IDisposable _watch(ServiceContext service_context, WatchOptions options) {
if (options.accounts) {
// watch accounts
return WatchAccounts.run(service_context.locate<AdminService>());
} else if (options.securities) {
// watch securities
var security_predicate = CLIUtils.parse_security_predicate(options.filter_symbols, options.filter_securities);
return WatchSecurities.run(service_context.locate<MarketDataService>(), security_predicate);
} else if (options.bbo) {
// watch bbo
return WatchBooks.run(new bts.md.MarketDataClient(),
CLIUtils.parse_security_predicate(options.filter_symbols, options.filter_securities));
} else if (options.symbols) {
// watch symbols
return WatchSymbols.run(service_context.locate<MarketDataService>());
} else if (options.orders) {
// watch orders (optionally filtered by account)
var account_predicate = CLIUtils.parse_delimited_predicate<OrderRecordImmutable>(
options.filter_accounts, order => order.Def.Account);
return WatchOrders.run(service_context.locate<OrderService>(), account_predicate);
} else if (options.positions) {
// watch positions (optionally filtered by account)
var account_predicate = CLIUtils.parse_delimited_predicate<PositionUnitImmutable>(
options.filter_accounts, pos => pos.Account);
return WatchPositions.run(service_context.locate<PositionService>(), account_predicate);
}
return null;
}
[Option("subscribe-positions", Required = false, HelpText = "run position-subscription example.")]
public bool subscribe_positions { get; set; }
[Option("subscribe-books", Required = false, HelpText = "run book-subscription example.")]
public bool subscribe_book { get; set; }
[Option("send-orders", Required = false, HelpText = "run send-orders example.")]
public bool send_orders { get; set; }
// if none are enabled, all options will be flipped on,
// so the user need not specify a single argument to see the examples run.
public bool any_enabled =>
subscribe_securities ||
subscribe_orders ||
subscribe_positions ||
subscribe_book ||
send_orders;
static IDisposable _send_orders(ServiceContext service_context, SendOrdersOptions options) {
var custom_order_def = OrderUtils.custom_order_definitioni(options.order_type);
if (!custom_order_def.HasValue) {
Console.WriteLine($"Unable to create order of type '{options.order_type}.' The following types are available:\n\t{string.Join("\n\t", OrderUtils.known_order_types)}");
return null;
}
var user = new NameIDPair(service_context.user, service_context.user_id);
var admin_service = service_context.locate<AdminService>();
var account = AdminUtils.get_account_async(user, admin_service, options.account).WaitResult();
var md_service = service_context.locate<MarketDataService>();
var security = SecurityUtils.get_security_async(md_service.subscribei_securities(), options.security).WaitResult();
var model = new OrderModel(service_context.locate<OrderService>());
/// Note that the call below (<see cref="OrderModel.send_order(NameIDPair, NameIDPair, uint, Side, uint, double, CustomOrderDefinition)"/>)
/// blocks the current thread until an ack is received from the server. If you wish to avoid blocking the thread as such,
/// use the following method instead: <see cref="OrderModel.send_order_async(NameIDPair, NameIDPair, uint, Side, uint, double, CustomOrderDefinition)"/>.
model.send_order(user, account, security.Id, options.side, options.quantity, options.price, custom_order_def.Value.CloneToMutable());
return null;
}
}
}
@@ -1,6 +1,6 @@
using System;
namespace bts.api.csharp {
namespace bts.api.csharp.data {
readonly struct NameIDPair {
public NameIDPair(string name, uint id) {
this.name = name ?? throw new ArgumentNullException(nameof(name));
@@ -0,0 +1,71 @@
using bts.api.csharp.utils;
using bts.api.orderman;
using System;
using System.Threading.Tasks;
namespace bts.api.csharp.data {
/// <summary>
/// Exemplifies how the <see cref="OrderService"/> api may be used to send orders.
/// </summary>
class OrderModel {
readonly OrderService _service;
public OrderModel(OrderService service) =>
_service = service;
public static OrderDefinition create_order_definition(NameIDPair user, NameIDPair account, uint security_key, Side side, CustomOrderDefinition custom_def) {
return new OrderDefinition {
Account = account.name,
AccountId = account.id,
User = user.name,
UserId = user.id,
SecurityKey = security_key,
Side = side,
CustomDef = custom_def
};
}
/// <summary>
/// Sends an order of the type specified by <paramref name="custom_order_definition"/> (asynchronous).
/// </summary>
/// <returns>The order id if the submission was successful, otherwise null.</returns>
public async Task<uint?> send_order_async(NameIDPair user, NameIDPair account, uint security_key, Side side, uint quantity, double price, CustomOrderDefinition custom_order_definition) {
Console.WriteLine($"Creating order {OrderUtils.fmt_order(side, quantity, price, user.name, account.name, custom_order_definition)}");
var response = await _service.create_order_async(new Create {
Quantity = quantity,
Price = price,
NewOrder = create_order_definition(user, account, security_key, side, custom_order_definition)
});
if (response.success) {
Console.WriteLine($"Order submission succeeded. Order id is {response.value.OrderKey}.");
return response.value.OrderKey;
} else {
Console.WriteLine($"Order submission failed: [{response.value.ErrorCode}] {response.value.Msg}.");
return null;
}
}
/// <summary>
/// Sends an order with specified parameters (the thread will be blocked until an ack is received from the server).
/// </summary>
public uint? send_order(NameIDPair user, NameIDPair account, uint security_key, Side side, uint quantity, double price, CustomOrderDefinition custom_order_definition) {
/// Note, the thread will be blocked until a response is received from the server.
/// For this reason, <see cref="OrderService.create_order_async(Create)"/> is usually preferred.
/// An example of how to use that method is provided in <see cref="send_order_async(NameIDPair, NameIDPair, CustomOrderDefinition, uint, double)"/>.
Console.WriteLine($"Creating order {OrderUtils.fmt_order(side, quantity, price, user.name, account.name, custom_order_definition)}");
var response = _service.create_order(new Create {
Quantity = quantity,
Price = price,
NewOrder = create_order_definition(user, account, security_key, side, custom_order_definition)
});
switch (response.ErrorCode) {
case OrderError.Ok:
Console.WriteLine($"Order submission succeeded. Order id is {response.OrderKey}.");
return response.OrderKey;
default:
Console.WriteLine($"Order submission failed: [{response.ErrorCode}] {response.Msg}.");
return null;
}
}
}
}
@@ -1,60 +0,0 @@
using bts.api.core;
using bts.api.csharp.utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using SecurityKey = System.UInt32;
namespace bts.api.csharp.examples {
/// <summary>
/// Uses the <see cref="MarketDataService"/> api to observe book updates
/// for security in the system, and organize them into a Dictionary.
/// </summary>
class BookExample : IDisposable {
readonly Dictionary<SecurityKey, BookEntryImmutable> _books = new Dictionary<SecurityKey, BookEntryImmutable>();
readonly IDisposable _subscription;
public BookExample(MarketDataService market_data_service) {
_subscription = market_data_service.subscribei_book()
.ObserveOn(SynchronizationContext.Current)
.Subscribe(_on_book_update);
}
public void Dispose() {
_subscription.Dispose();
}
void _on_book_update(Update<BookEntryImmutable, uint> update) {
foreach (var new_book in update.added) {
// of course, we shouldn't have entries for any books in update.added yet
Debug.Assert(!_books.ContainsKey(new_book.Key));
// display bbo in the console window
_print_book_info(new_book.Key, $"book received for the first time, bbo is: {BookUtils.fmt_bbo(new_book)}");
// add the new book to our map
_books.Add(new_book.Key, new_book);
}
foreach (var change in update.changes) {
// of course, the security key shouldn't change
Debug.Assert(change.Old.Key == change.New.Key);
// our book entry for this security should be identitical to change.Old
Debug.Assert(change.Old.Bid.SequenceEqual(_books[change.Old.Key].Bid));
Debug.Assert(change.Old.Ask.SequenceEqual(_books[change.Old.Key].Ask));
// display the current book in the console window
_print_book_info(change.New.Key, $"book changed, now bbo is: {BookUtils.fmt_bbo(change.New)}");
// update our map with the changed book
_books[change.New.Key] = change.New;
}
foreach (var old_book in update.removed) {
// of course, we should have entries for all books in update.removed
Debug.Assert(_books.Remove(old_book.Key));
}
}
static void _print_book_info(SecurityKey key, string message) {
Console.WriteLine($"Security {key}: {message}.");
}
}
}
@@ -1,139 +0,0 @@
using bts.api.core;
using bts.api.orderman;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using AccountKey = System.UInt32;
using OrderKey = System.UInt32;
namespace bts.api.csharp.examples {
/// <summary>
/// Uses the <see cref="OrderService"/> to observe order updates,
/// and group them by account.
/// </summary>
class OrderExample : IDisposable {
readonly Dictionary<AccountKey, Dictionary<OrderKey, OrderRecordImmutable>> _data = new Dictionary<AccountKey, Dictionary<AccountKey, OrderRecordImmutable>>();
IDisposable _subscription;
public OrderExample(OrderService order_service) {
/// 'OrderService' has 2 methods which can be used to subscribe to order updates.
/// -------------------------------------------------------------------------------------
/// 1) <see cref="OrderService.subscribe_orders(string)"/>.
/// 2) <see cref="OrderService.subscribei_orders(string)"/>.
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
/// --------------------------------------------------------------------------------------
_subscription = order_service.subscribei_orders()
// the 'ObserveOn' call below ensures that the
// callback will be invoked this thread.
// Be sure to do this if you don't want to worry
// about multi-threading issues (locking, etc).
.ObserveOn(SynchronizationContext.Current)
.Subscribe(_on_update);
}
public event Action<AccountKey> account_orders_changed;
public void Dispose() {
_subscription.Dispose();
}
public IEnumerable<OrderRecordImmutable> get_account_orders(AccountKey account) {
if (_data.TryGetValue(account, out var account_orders)) {
return account_orders.Values;
}
return Enumerable.Empty<OrderRecordImmutable>();
}
void _on_order_added(OrderRecordImmutable new_order) {
if (!_data.TryGetValue(new_order.Def.AccountId, out var account_orders)) {
// this is the first order for this account.
// initialize the account orders dictionary, and add it to our data.
account_orders = new Dictionary<AccountKey, OrderRecordImmutable>();
_data.Add(new_order.Def.AccountId, account_orders);
}
// now add the order to account_orders
account_orders.Add(new_order.Key, new_order);
}
void _on_order_changed(Change<OrderRecordImmutable> change) {
// ch.New and ch.Old have the same key.
Debug.Assert(change.New.Key == change.Old.Key);
var key = change.New.Key;
// react to a changed order
if (change.New.State.FilledQuantity != change.Old.State.FilledQuantity) {
// Display new fills in the console window.
var num_new_fills = change.New.State.FilledQuantity - change.Old.State.FilledQuantity;
_print_order_info(key, $"received {num_new_fills} new fills.");
// Be mindful that orders can be overfilled.
if (change.New.State.FilledQuantity > change.New.State.Quantity) {
// Display overfills in the console window.
var num_over_fills = change.New.State.FilledQuantity - change.New.State.Quantity;
_print_order_info(key, $"overfilled by {num_over_fills}");
} else {
// Display working qty in the console window.
var working_qty = change.New.State.Quantity - change.New.State.FilledQuantity;
_print_order_info(key, $"still working {working_qty}.");
}
}
// Display order status changes in the console window.
if (change.New.State.Status != change.Old.State.Status) {
_print_order_info(key, $"status changed from {change.Old.State.Status} to {change.New.State.Status}.");
}
// Account will not change, so our dictionary will definitely have a key for this account.
Debug.Assert(_data.ContainsKey(change.New.Def.AccountId));
var account_orders = _data[change.New.Def.AccountId];
// The value for this key in account_orders should equal ch.Old.
Debug.Assert(account_orders[key].Equals(change.Old));
// update account orders with the changed order.
account_orders[key] = change.New;
}
void _on_order_removed(OrderRecordImmutable old_val) {
// An entry for this order should be present in our data,
// as it is in the 'removed' items.
// Assert this is the case to illustrate the point.
Debug.Assert(_data.ContainsKey(old_val.Def.AccountId));
var account_orders = _data[old_val.Def.AccountId];
// the removed value should match the one currently in our data.
// Assert this is the case, then remove the order.
Debug.Assert(account_orders[old_val.Key].Equals(old_val));
account_orders.Remove(old_val.Key);
_print_order_info(old_val.Key, "has been removed.");
}
void _on_update(Update<OrderRecordImmutable, OrderKey> update) {
// record which accounts experienced an order update, then
// at the end of this method, fire the account_orders_changed event for each
var accounts_with_order_updates = new HashSet<AccountKey>();
foreach (OrderRecordImmutable new_order in update.added) {
_on_order_added(new_order);
accounts_with_order_updates.Add(new_order.Def.AccountId);
}
foreach (Change<OrderRecordImmutable> change in update.changes) {
_on_order_changed(change);
accounts_with_order_updates.Add(change.New.Def.AccountId);
}
foreach (OrderRecordImmutable old_order in update.removed) {
_on_order_removed(old_order);
accounts_with_order_updates.Add(old_order.Def.AccountId);
}
// display all accounts with order updates in the Console window.
Console.WriteLine("The following accounts experienced order changes: ");
foreach (var account in accounts_with_order_updates) {
Console.WriteLine("\n\t" + account);
}
// lastly, fire the account_orders_changed event for each account experienced an order update.
foreach (var account in accounts_with_order_updates) {
account_orders_changed?.Invoke(account);
}
}
void _print_order_info(OrderKey order_key, string message) {
Console.WriteLine($"Order {order_key}: {message}.");
}
}
}
@@ -1,125 +0,0 @@
using bts.api.core;
using bts.api.position;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reactive.Linq;
using System.Threading;
namespace bts.api.csharp.examples {
/// <summary>
/// A pair of security, and account id,
/// which uniquely identifies each PositionUnit.
/// </summary>
readonly struct PositionKey {
public PositionKey(uint security_id, uint account_id) {
this.security_id = security_id;
this.account_id = account_id;
}
public uint security_id { get; }
public uint account_id { get; }
public static PositionKey from_position(PositionUnitImmutable p) =>
new PositionKey(p.SecurityId, p.AccountId);
public override string ToString() =>
$"Sec {security_id}, Acc {account_id}";
}
/// <summary>
/// Uses the <see cref="PositionService"/> api to monitor positions
/// for every account/security pair, and organize them into a Dictionary.
/// </summary>
class PositionExample : IDisposable {
readonly Dictionary<PositionKey, PositionUnitImmutable> _data = new Dictionary<PositionKey, PositionUnitImmutable>();
IDisposable _subscription;
public PositionExample(PositionService position_service) {
/// 'PositionService' has 2 methods which can be used to subscribe to position updates.
/// -------------------------------------------------------------------------------------
/// 1) <see cref="PositionService.subscribe_position_data(string)"/>.
/// 2) <see cref="PositionService.subscribei_position_data(string)"/>.
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
/// --------------------------------------------------------------------------------------
_subscription = position_service.subscribei_position_data()
// the 'ObserveOn' call below ensures that the
// callback will be invoked this thread.
// Be sure to do this if you don't want to worry
// about multi-threading issues (locking, etc).
.ObserveOn(SynchronizationContext.Current)
.Subscribe(_on_update);
}
public event Action data_changed;
public IReadOnlyDictionary<PositionKey, PositionUnitImmutable> data => _data;
public void Dispose() =>
_subscription.Dispose();
void _on_position_added(PositionUnitImmutable new_val) {
var k = PositionKey.from_position(new_val);
// this entry should NOT be present in our _data, as it is in the 'added' items.
// Assert this is the case to illustrate the point.
Debug.Assert(!_data.ContainsKey(k));
_print_position_info(k, $"initial position received. Day: Long {new_val.DayLongPos}, Short {new_val.DayShortPos}, Net {new_val.DayLongPos - new_val.DayShortPos}.");
// add the new position unit to _data
_data.Add(k, new_val);
}
void _on_position_changed(Change<PositionUnitImmutable> change) {
var k = PositionKey.from_position(change.New);
// ch.New and ch.Old have the same key (i.e., the Account/Security pair),
Debug.Assert(change.New.AccountId == change.Old.AccountId);
Debug.Assert(change.New.SecurityId == change.Old.SecurityId);
// of course, the value for this key in our dictionary should equal ch.Old.
Debug.Assert(_data[k].Equals(change.Old));
// Display changes to the day position in the console window.
// 1) check if the day long changed
if (change.New.DayLongPos != change.Old.DayLongPos) {
Console.WriteLine($"[{k}] day long changed from {change.Old.DayLongPos} to {change.New.DayLongPos}.");
}
// 2) check if day short changed
if (change.New.DayShortPos != change.Old.DayShortPos) {
Console.WriteLine($"[{k}] day short changed from {change.Old.DayShortPos} to {change.New.DayShortPos}.");
}
// 3) check if net day position changed
var new_net_day_pos = change.New.DayLongPos - change.New.DayShortPos;
var old_net_day_pos = change.Old.DayLongPos - change.Old.DayShortPos;
if (new_net_day_pos != old_net_day_pos) {
Console.WriteLine($"[{k}] day net changed from {old_net_day_pos} to {new_net_day_pos}.");
}
// update _data with the changed position.
_data[k] = change.New;
}
void _on_position_removed(PositionUnitImmutable old_val) {
var k = PositionKey.from_position(old_val);
// this entry should be present in our _data, as it is in the 'removed' items.
// Assert this is the case to illustrate the point.
Debug.Assert(_data.ContainsKey(k));
// this should match our data. Assert this is the case.
Debug.Assert(_data[k].Equals(old_val));
_data.Remove(k);
Console.WriteLine($"[{k}] position has been removed.");
}
void _on_update(Update<PositionUnitImmutable, int> update) {
foreach (PositionUnitImmutable p in update.added) {
_on_position_added(p);
}
foreach (Change<PositionUnitImmutable> ch in update.changes) {
_on_position_changed(ch);
}
foreach (PositionUnitImmutable p in update.removed) {
_on_position_removed(p);
}
data_changed?.Invoke();
}
void _print_position_info(PositionKey position_key, string message) {
Console.WriteLine($"Position {position_key}: {message}.");
}
}
}
@@ -1,48 +0,0 @@
using bts.api.core;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using SecurityKey = System.UInt32;
namespace bts.api.csharp.examples {
/// <summary>
/// Uses the <see cref="MarketDataService"/> api to observe the
/// state of securities in the system.
/// </summary>
class SecurityExample : IDisposable {
readonly IDisposable _subscription;
readonly Dictionary<SecurityKey, SecurityImmutable> _securities = new Dictionary<SecurityKey, SecurityImmutable>();
public SecurityExample(MarketDataService market_data_service) {
_subscription = market_data_service.subscribei_securities()
.ObserveOn(SynchronizationContext.Current)
.Subscribe(_on_securities_update);
}
public void Dispose() {
_subscription.Dispose();
}
void _on_securities_update(Update<SecurityImmutable, uint> update) {
foreach (var new_security in update.added) {
// of course, we shouldn't have entries for any books in update.added yet
Debug.Assert(!_securities.ContainsKey(new_security.Id));
// display bbo in the console window
Console.WriteLine($"received new security: {new_security}.");
// add the new security to our map
_securities.Add(new_security.Id, new_security);
}
// As you may have noticed, all of the subcribe methods in the api
// return an IObservable<Update<T>>. It is worth noting that for security updates,
// 'changes' will always be empty.
Debug.Assert(!update.changes.Any());
foreach (var old_security in update.removed) {
// of course, we should have entries for all books in update.removed
Debug.Assert(_securities.Remove(old_security.Id));
}
}
}
}
@@ -1,110 +0,0 @@
using bts.api.csharp.utils;
using bts.api.orderman;
using System;
using System.Threading.Tasks;
namespace bts.api.csharp.examples {
static class OrderTemplates {
public static CustomOrderDefinition limit() => new CustomOrderDefinition { Limit = new LimitOrderDefinition() };
public static CustomOrderDefinition market() => new CustomOrderDefinition { Market = new MarketOrderDefinition() };
public static CustomOrderDefinition iceberg(double show_quantity) => iceberg(show_quantity, limit());
public static CustomOrderDefinition iceberg(double show_quantity, CustomOrderDefinition child) =>
new CustomOrderDefinition {
Iceberg = new IcebergOrderDefinition {
ShowQuantity = show_quantity,
Child = child
}
};
}
/// <summary>
/// Exemplifies how the <see cref="OrderService"/> api may be used to
/// send several common order types (limit, market, and iceberg).
/// </summary>
class SendOrderModel {
readonly OrderService _service;
public SendOrderModel(OrderService service) {
_service = service;
}
public OrderDefinition create_order_definition(NameIDPair user, NameIDPair account, uint security_key, CustomOrderDefinition custom_def) {
return new OrderDefinition {
Account = account.name,
AccountId = account.id,
User = user.name,
UserId = user.id,
SecurityKey = security_key,
CustomDef = custom_def
};
}
/// <summary>
/// Sends a limit order (asynchronous).
/// </summary>
/// <returns>The order id if the submission was successful, otherwise null.</returns>
public Task<uint?> send_limit_order_async(NameIDPair user, NameIDPair account, uint security_key, uint quantity, double price) {
return send_order_async(user, account, security_key, OrderTemplates.limit(), quantity, price);
}
/// <summary>
/// Sends a market order (asynchronous). Note no price parameter is required for market orders.
/// </summary>
/// <returns>The order id if the submission was successful, otherwise null.</returns>
public Task<uint?> send_market_order_async(NameIDPair user, NameIDPair account, uint security_key, uint quantity) {
return send_order_async(user, account, security_key, OrderTemplates.market(), quantity, 0.0);
}
/// <summary>
/// Sends an iceberg of limit orders (asynchronous).
/// </summary>
/// <returns>The order id if the submission was successful, otherwise null.</returns>
public Task<uint?> send_iceberg_order_async(NameIDPair user, NameIDPair account, uint security_key, uint quantity, double price, double show_quantity) {
return send_order_async(user, account, security_key, OrderTemplates.iceberg(show_quantity), quantity, price);
}
/// <summary>
/// Sends an order of the type specified by <paramref name="custom_order_definition"/> (asynchronous).
/// </summary>
/// <returns>The order id if the submission was successful, otherwise null.</returns>
public async Task<uint?> send_order_async(NameIDPair user, NameIDPair account, uint security_key, CustomOrderDefinition custom_order_definition, uint quantity, double price) {
Console.WriteLine($"submitting order for user ({user}), account ({account}): {OrderUtils.fmt_order(quantity, price, custom_order_definition)}.");
var reply = await _service.create_order_async(new Create {
Quantity = quantity,
Price = price,
NewOrder = create_order_definition(user, account, security_key, custom_order_definition)
});
if (reply.success) {
Console.WriteLine($"received ack that order has been submitted. Order id is {reply.value.OrderKey}.");
return reply.value.OrderKey;
} else {
Console.WriteLine($"order submission was unsuccessful: [{reply.value.ErrorCode}] {reply.value.Msg}.");
return null;
}
}
public void send_order(NameIDPair user, NameIDPair account, uint security_key, CustomOrderDefinition custom_order_definition, uint quantity, double price) {
/// Note, the thread will be blocked until a response is received from the server.
/// For this reason, <see cref="OrderService.create_order_async(Create)"/> is usually preferred.
/// An example of how to use that method is provided in <see cref="send_order_async(NameIDPair, NameIDPair, CustomOrderDefinition, uint, double)"/>.
Console.WriteLine($"Creating order {OrderUtils.fmt_order(quantity, price, custom_order_definition)}");
var response = _service.create_order(new Create {
Quantity = quantity,
Price = price,
NewOrder = create_order_definition(user, account, security_key, custom_order_definition)
});
switch (response.ErrorCode) {
case OrderError.Ok:
_print_order_info(response.OrderKey, "successfully created.");
break;
default:
Console.WriteLine($"Failed to create order: [{response.ErrorCode}] {response.Msg}.");
break;
}
}
static void _print_order_info(uint order_key, string message) {
Console.WriteLine($"Order {order_key}: {message}.");
}
}
}
@@ -1,100 +0,0 @@
using bts.api.core;
using bts.api.orderman;
using mbts;
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace bts.api.csharp.examples {
/// <summary>
/// The code in this example is a bit more advanced than the others,
/// as it is designed to send orders, asynchronously, in a continuous loop.
/// For straightforward examples of how to send orders with the api,
/// check out <see cref="SendOrderModel"/>.
/// </summary>
static class SendOrderLoop {
/// <summary>
/// As a security key is required to send an order, this method
/// uses the <see cref="MarketDataService"/> to observe the securities stream,
/// then on the first update, initializes the send-order-loop <see cref="LoopImpl"/>
/// with one of the newly received security keys.
/// </summary>
public static IDisposable init_loop(OrderService order_service, MarketDataService market_data_service) {
// If you need to familiarize yourself with the IObservable pattern, check out the official docs:
// https://docs.microsoft.com/en-us/dotnet/api/system.iobservable-1?view=netcore-3.1
var single_security_stream = market_data_service.subscribei_securities()
.Where(u => u.all.Any())
.Take(1)
.Select(u => u.all.FirstOrDefault());
return init_loop(single_security_stream, create_loop);
IDisposable create_loop(SecurityImmutable sec) {
return new LoopImpl(order_service, sec.Id);
}
}
/// <summary>
/// Observes the security stream and creates a new send-order-loop
/// whenever a new security arrives. The previous send-order-loop
/// is disposed when the next security arrives, so only one send-order-loop
/// shall be active at a time.
/// </summary>
/// <param name="security">The security stream used to initialize send-order-loops.</param>
/// <returns>An IDisposable that unsubsribes from the securities stream, and disposes the active send-order-loop.</returns>
public static IDisposable init_loop(IObservable<SecurityImmutable> security, Func<SecurityImmutable, IDisposable> factory) {
IDisposable active_loop = null;
var security_sub = security.Subscribe(s =>
Interlocked.Exchange(ref active_loop, factory(s))?.Dispose());
return Disposable.Create(dispose);
void dispose() {
security_sub.Dispose();
active_loop?.Dispose();
}
}
class LoopImpl : IDisposable {
bool _is_disposed;
public LoopImpl(OrderService service, uint security_key)
: this(service, Users.Default, Accounts.Default, security_key, 10, TimeSpan.FromSeconds(3)) { }
public LoopImpl(OrderService service, NameIDPair user, NameIDPair account, uint security_key, int max_orders, TimeSpan delay_between_orders) {
var model = new SendOrderModel(service);
send_orders_continuously();
async void send_orders_continuously() {
// Periodically send an order with one of the CustomOrderDefinitions below.
// If you are unfamiliar with async/await, this will also serve as a simple example
// of how those keywords may be used. However, there is no substite for the official docs:
// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
var order_templates = new CustomOrderDefinitionImmutable[] {
new CustomOrderDefinitionImmutable(OrderTemplates.limit()),
new CustomOrderDefinitionImmutable(OrderTemplates.market()),
new CustomOrderDefinitionImmutable(OrderTemplates.iceberg(5))
};
for (int num_orders_sent = 0; num_orders_sent < max_orders && !_is_disposed; num_orders_sent++) {
int template_index = num_orders_sent % order_templates.Length;
CustomOrderDefinitionImmutable templatei = order_templates[template_index];
// The order templates we created are immutable, however the api uses the mutable form.
// Not to worry tho, there is a nifty method called 'CloneToMutable.'
// Note that all of the non-primitive immutable parameters in the api methods have a 'CloneToMutable' method,
// it is not unique to CustomOrderDefinitionImmutable.
CustomOrderDefinition template = templatei.CloneToMutable();
await model.send_order_async(user, account, security_key, template, 12, 100);
// pause for a moment before sending the next order
await Task.Delay(delay_between_orders);
}
}
}
public void Dispose() {
// flip _is_disposed to true, so the 'send_orders_continuously' loop
// knows it's time to stop.
_is_disposed = true;
}
}
}
}
@@ -0,0 +1,79 @@
using bts.api.admin;
using bts.api.csharp.data;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace bts.api.csharp.utils {
static class AdminUtils {
public static IReadOnlyDictionary<uint, NameIDPair> map_accounts(AdminDataImmutable admin_datai) {
return admin_datai.Accounts.ToDictionary(acc => acc.Id, acc => new NameIDPair(acc.Name, acc.Id));
}
/// <summary>
/// Links users to the set of accounts on which they have trading permissions.
/// </summary>
public static IEnumerable<(NameIDPair user, IEnumerable<uint> accounts)> user_account_permissions(AdminDataImmutable admin_datai) {
return admin_datai.Users
.Select(user => (new NameIDPair(user.Name, user.Id), _user_tradable_accounts(user)));
}
public static IReadOnlyDictionary<NameIDPair, ImmutableHashSet<NameIDPair>> map_user_account_permissions(AdminDataImmutable admin_datai) {
var account_lookup = admin_datai.Accounts.ToLookup(acc => acc.Id);
return admin_datai.Users.ToDictionary(
user => new NameIDPair(user.Name, user.Id),
user => ImmutableHashSet.CreateRange(_user_tradable_accounts(user)
.Select(acc_id => new NameIDPair(account_lookup[acc_id].Single().Name, acc_id))));
}
static IEnumerable<uint> _user_tradable_accounts(BTSUserImmutable user) {
return user.UserData.AccountPermissions.AccountPermissions
.Where(kv => kv.Value.TradingPermission.HasPermissions)
.Select(kv => kv.Key);
}
public static NameIDPair get_account(NameIDPair user, AdminService admin_service, string account_name = null) {
var task = get_account_async(user, admin_service, account_name);
task.Wait();
return task.Result;
}
/// <summary>
/// Either searches for the account with the specified name,
/// or picks a random account for which the specified user has trading permissions.
/// </summary>
public static async Task<NameIDPair> get_account_async(NameIDPair user, AdminService admin_service, string account_name = null) {
return await (account_name == null ? auto_account(user, admin_service) : find_account(admin_service, account_name).FirstAsync());
}
public static IObservable<NameIDPair> auto_account(NameIDPair user, AdminService admin_service) {
return admin_service.subscribei_admin_data()
.Where(update => update.all.Any())
.Select(update => map_user_account_permissions(update.all.First()))
.Select(account_perms => account_perms.TryGetValue(user, out var accs) ? accs : ImmutableHashSet<NameIDPair>.Empty)
.Where(accs => accs.Count > 0)
.Select(accs => accs.First())
.Take(1);
}
public static IObservable<NameIDPair> find_account(AdminService admin_service, string account) {
return admin_service.subscribei_admin_data()
.Select(update => {
foreach (var admin_data in update.all) {
foreach (var acc in admin_data.Accounts) {
if (acc.Name == account)
return new NameIDPair(acc.Name, acc.Id);
}
}
return (NameIDPair?)null;
})
.Where(acc => acc.HasValue)
.Select(acc => acc.Value)
.Take(1);
}
}
}
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace bts.api.csharp.utils {
static class CLIUtils {
public static IEnumerable<string> parse_delimited(string cli_arg, string file_arg, string delimiter) {
var cli_items = _parse_delimited(cli_arg, delimiter);
if (file_arg != null) {
try {
var items = _parse_delimited(File.ReadAllText(file_arg), delimiter);
return cli_items is null ? items : cli_items.Concat(items);
} catch { }
}
return cli_items;
}
public static Func<SecurityImmutable, bool> parse_security_predicate(string cli_symbols = null, string cli_security_regex = null) {
return _merge_predicates(
parse_delimited_predicate<SecurityImmutable>(cli_symbols, sec => sec.Symbol),
RegexUtils.default_regex(cli_security_regex)?.to_predicate<SecurityImmutable>());
}
public static Func<T, bool> parse_delimited_predicate<T>(string cli_arg, Func<T, string> get) =>
parse_delimited_predicate(cli_arg, null, ",", get);
public static Func<T, bool> parse_delimited_predicate<T>(string cli_arg, string file_arg, string delimiter, Func<T, string> get) {
var items = new HashSet<string>(parse_delimited(cli_arg, file_arg, delimiter));
if (items.Count == 0)
return null;
return sec => items.Contains(get(sec));
}
static IEnumerable<string> _parse_delimited(string delimited_string, string delimiter) {
return delimited_string
?.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries)
?.Select(sym => sym.Trim())
?? Enumerable.Empty<string>();
}
static Func<T, bool> _merge_predicates<T>(params Func<T, bool>[] predicates) {
Func<T, bool> result = null;
for (int i = 0; i < predicates.Length; i++) {
if (predicates[i] != null) {
if (result == null) {
result = predicates[i];
} else {
result += predicates[i];
}
}
}
return result;
}
}
}
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace bts.api.csharp.utils {
static class CollectionUtils {
public static bool insert_sorted<T>(List<T> list, T value) =>
insert_sorted(list, value, Comparer<T>.Default);
public static bool insert_sorted<T>(List<T> list, T value, IComparer<T> comparer) {
if (list is null)
throw new ArgumentNullException(nameof(list));
if (comparer is null)
throw new ArgumentNullException(nameof(comparer));
var sorted_index = list.BinarySearch(value, comparer);
if (sorted_index < 0) {
// this is a new symbol
list.Insert(~sorted_index, value);
return true;
}
return false;
}
}
}
@@ -0,0 +1,8 @@
using System;
namespace bts.api.csharp.utils {
static class DelegateUtils {
public static Func<TOuter, TResult> wrap<TInner, TOuter, TResult>(this Func<TInner, TResult> func, Func<TOuter, TInner> get_inner) =>
outer => func(get_inner(outer));
}
}
@@ -1,9 +1,32 @@
using bts.api.orderman;
using System.Collections.Generic;
using System.Linq;
namespace bts.api.csharp.utils {
static class OrderUtils {
// add new templates as needed
static readonly IReadOnlyDictionary<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinitionImmutable>
_custom_def_templates = new Dictionary<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinitionImmutable> {
{ CustomOrderDefinition.CustomOneofCase.Limit, new CustomOrderDefinitionImmutable(new CustomOrderDefinition{ Limit = new LimitOrderDefinition() }) },
{ CustomOrderDefinition.CustomOneofCase.Market, new CustomOrderDefinitionImmutable(new CustomOrderDefinition{ Market = new MarketOrderDefinition() }) },
{ CustomOrderDefinition.CustomOneofCase.Iceberg, new CustomOrderDefinitionImmutable(new CustomOrderDefinition{ Iceberg = new IcebergOrderDefinition() }) }
};
public static IEnumerable<CustomOrderDefinition.CustomOneofCase> known_order_types => _custom_def_templates.Keys;
public static CustomOrderDefinitionImmutable? custom_order_definitioni(CustomOrderDefinition.CustomOneofCase order_type) {
if (_custom_def_templates.TryGetValue(order_type, out var templatei)) {
return templatei.CloneToMutable();
}
return null;
}
public static string fmt_order(uint quantity, double price, CustomOrderDefinition custom_order_definition) {
return $"{quantity} @ {price}: {custom_order_definition}";
}
public static string fmt_order(Side side, uint quantity, double price, string user, string account, CustomOrderDefinition custom_order_definition) {
return $"[{user}][{account}] {side} {fmt_order(quantity, price, custom_order_definition)}.";
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Text.RegularExpressions;
namespace bts.api.csharp.utils {
static class RegexUtils {
public static Regex default_regex(string pattern) {
// these patterns typically come from the user,
// so the 'should_ignore' delegate below allows them
// to include all results by specifying "all" or "*".
return default_regex(pattern, p => string.IsNullOrEmpty(p) || p == "*" || p.ToLower() == "all");
}
public static Regex default_regex(string pattern, Func<string, bool> should_ignore) {
if (pattern == null)
return null;
pattern = pattern.Trim();
if (pattern == string.Empty)
return null;
return new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
}
public static Func<T, bool> to_predicate<T>(this Regex regex) =>
regex == null ? null : new Func<T, bool>(val => regex.IsMatch(val.ToString()));
}
}
@@ -1,8 +1,52 @@
using System;
using bts.api.core;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using SymbolTableGroup = bts.api.core.TableUpdateGrouping<bts.api.SecurityImmutable, uint, string>;
namespace bts.api.csharp.utils {
static class SecurityUtils {
public static IObservable<TableUpdate<SymbolTableGroup, string>> GroupBySymbols(this IObservable<TableUpdate<SecurityImmutable, uint>> securities_source) =>
securities_source.GroupTable(sec => sec.Id, sec => sec.Symbol);
public static async Task<SecurityImmutable> get_security_async(IObservable<Update<SecurityImmutable, uint>> source, string security_str = null) {
if (!string.IsNullOrEmpty(security_str)) {
Func<SecurityImmutable, bool> is_match;
// the user may specify the security string, or security key
if (uint.TryParse(security_str, out var sec_key)) {
is_match = sec => sec.Id == sec_key;
} else {
// ignore case and white space
security_str = security_str.Replace(" ", "").ToLower();
is_match = sec => sec.ToString().Replace(" ", "").ToLower() == security_str;
}
return await source.Select(update => {
foreach (var sec in update.all) {
if (is_match(sec))
return sec;
}
return (SecurityImmutable?)null;
})
.Where(sec => sec.HasValue)
.Select(sec => sec.Value)
.FirstAsync();
} else {
return await source.Where(update => update.all.Any())
.Select(update => update.all.First())
.FirstAsync();
}
}
public static class Predicates {
public static Func<SecurityImmutable, bool> symbols(IEnumerable<string> symbols) {
if (symbols is null)
return sec => true;
// use hashset for quicker Contains check
var symbols_set = new HashSet<string>(symbols);
return sec => symbols_set.Contains(sec.Symbol);
}
}
}
}
@@ -0,0 +1,40 @@
using bts.api.core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace bts.api.csharp.utils {
static class StringUtils {
public static IEnumerable<string> format_columns(IEnumerable<IEnumerable<string>> columns, int min_width = 8, int padding = 3) =>
format_columns(columns.Select(lines => (Math.Max(min_width, lines.Max(l => l.Length) + padding), lines)));
public static IEnumerable<string> format_columns(IEnumerable<(int width, IEnumerable<string> lines)> columns) {
var column_layout = columns.Select((column, index) => (column.lines.GetEnumerator(), column.width, index)).ToList();
int num_active = column_layout.Count;
if (num_active == 0)
yield break;
while (true) {
var sb = new StringBuilder();
for (int i = 0; i < column_layout.Count; i++) {
var (en, width, pos) = column_layout[i];
if (en != null && en.MoveNext()) {
sb.Append(string.Format($"{{{0},{-width}}}", en.Current));
sb.Append(string.Format($"{{{0,1}}}", "| "));
} else {
sb.Append(string.Format($"{{{0},{-width}}}", ""));
sb.Append(string.Format($"{{{0,1}}}", "| "));
if (en != null) {
// set the enumerable to null to inidicate this column is empty for the remaining rows
en.Dispose();
column_layout[i] = (null, width, pos);
if (--num_active == 0)
yield break;
}
}
}
yield return sb.ToString();
}
}
}
}
@@ -0,0 +1,10 @@
using System.Threading.Tasks;
namespace bts.api.csharp.utils {
static class TaskExtensions {
public static T WaitResult<T>(this Task<T> task) {
task.Wait();
return task.Result;
}
}
}
@@ -0,0 +1,114 @@
using bts.api.core;
using mbts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace bts.api.csharp.utils {
static class UpdateUtils {
/// <summary>
/// Uses a value-subscription delegate to create subscriptions (incrementally) for each value in the update sequence.
/// When a value is removed or changed, its corresponding subscription is disposed; if it changed, a new subscription is created with the new value.
/// </summary>
/// <typeparam name="T">The type of values in the updates.</typeparam>
/// <typeparam name="TKey">The type of keys in the updates.</typeparam>
/// <param name="update_stream">The update sequence.</param>
/// <param name="subscribe_value">The delegate to create a subscription for a particular value.</param>
/// <returns>An IDisposable that, upon being disposed, disposes the subscription to the update sequence, and all active value subscriptions.</returns>
public static IDisposable subscribe_incremental_values<T, TKey>(IObservable<Update<T, TKey>> update_stream, Func<T, IDisposable> subscribe_value)
where TKey : IEquatable<TKey> {
var locker = new object();
var value_subs = new Dictionary<TKey, IDisposable>();
var source_sub = update_stream.Subscribe(on_update);
return Disposable.Create(dispose);
// local methods
void on_update(Update<T, TKey> update) {
lock (locker) {
foreach (var value in update.removed) {
var k = update.key(value);
value_subs[k].Dispose();
value_subs.Remove(k);
}
foreach (var ch in update.changes) {
var k = update.key(ch.New);
value_subs[k].Dispose();
value_subs[k] = subscribe_value(ch.New);
}
foreach (var value in update.added) {
value_subs.Add(update.key(value), subscribe_value(value));
}
}
}
void dispose() {
lock (locker) {
source_sub.Dispose();
foreach (var loop in value_subs.Values) {
loop.Dispose();
}
}
}
}
public static IObservable<TableUpdate<T, TKey>> SafeFilter<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, Func<T, bool> filter) where TKey : IEquatable<TKey> =>
filter == null ? source : source.Filter(filter);
public static bool is_empty<T, TKey>(this TableUpdate<T, TKey> table) where TKey : IEquatable<TKey> =>
!table.added.Any() && !table.changes.Any() && !table.removed.Any();
public static void print_incremental_changes<T, TKey>(Update<T, TKey> update, Func<T, TKey> key_fn, Func<T, string> fmt_value, string title) where TKey :IEquatable<TKey> {
print_incremental_changes<T, TKey>(update.added, update.changes, update.removed, v => $"{key_fn(v)}: {fmt_value(v)}", title);
}
public static void print_incremental_changes<T, TKey>(TableUpdate<T, TKey> table, Func<T, TKey> key_fn, Func<T, string> fmt_value, string title) where TKey :IEquatable<TKey> {
if (fmt_value is null)
fmt_value = v => v.ToString();
print_incremental_changes<T, TKey>(table.added, table.changes, table.removed, v => $"{key_fn(v)}: {fmt_value(v)}", title);
}
public static void print_incremental_changes<T, TKey>(IEnumerable<T> added, IEnumerable<Change<T>> changes, IEnumerable<T> removed, Func<T, string> fmt, string title) {
if (fmt is null)
fmt = v => v?.ToString() ?? "(null)";
var console_out = new StringBuilder();
Console.WriteLine($"Received {title} update");
var lines = StringUtils.format_columns(new[] {
added.Select(fmt).Prepend(" Added "),
changes.Select(ch => ch.New).Select(fmt).Prepend(" Changed "),
removed.Select(fmt).Prepend(" Removed ")
});
foreach (var l in lines)
Console.WriteLine("\t" + l);
}
public static void _print_incremental_changes<T, TKey>(IEnumerable<T> added, IEnumerable<Change<T>> changes, IEnumerable<T> removed, Func<T, string> fmt, string title) {
if (fmt is null)
fmt = v => v?.ToString() ?? "(null)";
var console_out = new StringBuilder();
console_out.AppendLine($"Received {title} update");
using (var added_en = added.GetEnumerator()) {
if (added_en.MoveNext()) {
console_out.AppendLine(" Added:");
do console_out.AppendLine(" " + fmt(added_en.Current));
while (added_en.MoveNext());
}
}
using (var changes_en = changes.GetEnumerator()) {
if (changes_en.MoveNext()) {
console_out.AppendLine(" Changes");
do console_out.AppendLine($" {fmt(changes_en.Current.Old)} --> {fmt(changes_en.Current.New)}");
while (changes_en.MoveNext());
}
}
using (var removed_en = removed.GetEnumerator()) {
if (removed_en.MoveNext()) {
console_out.AppendLine(" Removed");
do console_out.AppendLine(" " + fmt(removed_en.Current));
while (removed_en.MoveNext());
}
}
Console.Write(console_out.ToString());
}
}
}
@@ -0,0 +1,46 @@
using bts.api.admin;
using bts.api.core;
using bts.api.csharp.data;
using bts.api.csharp.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
namespace bts.api.csharp.watch {
static class WatchAccounts {
/// <summary>
/// Uses the <see cref="AdminService"/> to subscribe to administrative data (users, accounts, permissions, etc.),
/// and displays account updates in the console window.
/// </summary>
public static IDisposable run(AdminService admin_service) {
// extract accounts from the admin data into an IObservable<IReadOnlyDictionary<uint, NameIDPair>>.
IObservable<IReadOnlyDictionary<uint, NameIDPair>> accounts_source =
admin_service.subscribei_admin_data()
.Select(update => {
// Note that update.all is an IEnumerable<AdminDataImmutable>,
// however, it should only have one element. Pass the first element
// to the _map_accounts method, and return the resultant IReadOnlyDictionary.
return _map_accounts(update.all.FirstOrDefault());
});
// Use the 'Table' extension method to transform the accounts source from an IObservable<IReadOnlyDictionary<TKey, TValue>>
// into an IObservable<TableUpdate<TKey, TValue>> (the latter includes incremental update properties).
/// <see cref="TableUpdate{T, TKey}.added"/>
/// <see cref="TableUpdate{T, TKey}.changes"/>
/// <see cref="TableUpdate{T, TKey}.removed"/>
IObservable<TableUpdate<NameIDPair, uint>> incremental_accounts_source =
accounts_source.Table(account => account.id);
// subscribe to the incremental account updates,
// and print the changes in the console window.
return incremental_accounts_source
.ObserveOn(ThreadPoolScheduler.Instance)
.Subscribe(upd => UpdateUtils.print_incremental_changes(upd, acc => acc.id, acc => acc.name, "Accounts"));
}
static IReadOnlyDictionary<uint, NameIDPair> _map_accounts(AdminDataImmutable admin_data) =>
admin_data.Accounts.ToDictionary(acc => acc.Id, acc => new NameIDPair(acc.Name, acc.Id));
}
}
@@ -0,0 +1,48 @@
using bts.md;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using SecurityKey = System.UInt32;
namespace bts.api.csharp.watch {
static class WatchBooks {
/// <summary>
/// Uses the <see cref="MarketDataService"/> to observe book updates
/// for an optionally filtered list of securities in the system,
/// and organize them into a dictionary.
/// </summary>
public static IDisposable run(MarketDataClient md_client, Func<SecurityImmutable, bool> security_predicate = null) {
var books = new Dictionary<SecurityKey, IMDBook>();
object locker = new object();
if (md_client is null)
throw new ArgumentNullException(nameof(md_client));
if (security_predicate is null)
security_predicate = new Func<SecurityImmutable, bool>(sec => true);
/// unlike the other api subscriptions, which publish <see cref="core.Update{T, TKey}"/>
/// this subscription publishes an <see cref="IEnumerable{T}"/>, so the updates are not incremental
/// (i.e., don't have added/removed/changed items).
return md_client.subscribe_book(security_predicate)
.ObserveOn(ThreadPoolScheduler.Instance)
.Subscribe(on_update);
void on_update(IEnumerable<IMDBook> new_books) {
lock (locker) {
foreach (var book in new_books) {
Console.WriteLine($"{book.security} book updated: {_fmt_bbo(book)}.");
books[book.security.Id] = book;
}
}
}
}
static string _fmt_bbo(IMDBook book) {
return $"{_fmt_price_size(book.bids.FirstOrDefault())} <--> {_fmt_price_size(book.asks.FirstOrDefault())}";
}
static string _fmt_price_size(PriceSize price_size) {
return price_size == null ? "N/A" : $"{price_size.size} @ {price_size.price}";
}
}
}
@@ -0,0 +1,109 @@
using bts.api.core;
using bts.api.orderman;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using AccountKey = System.UInt32;
using OrderKey = System.UInt32;
namespace bts.api.csharp.watch {
static class WatchOrders {
/// <summary>
/// Uses the <see cref="OrderService"/> to observe order updates, and group them by account.
/// Fills and status changes are displayed in the console window.
/// </summary>
public static IDisposable run(OrderService order_service, Func<OrderRecordImmutable, bool> predicate) {
/// 'OrderService' has 2 methods which can be used to subscribe to order updates.
/// -------------------------------------------------------------------------------------
/// 1) <see cref="OrderService.subscribe_orders(string)"/>.
/// 2) <see cref="OrderService.subscribei_orders(string)"/>.
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
/// --------------------------------------------------------------------------------------
var orders_source = order_service.subscribei_orders();
if (predicate != null)
orders_source = orders_source.Filter(predicate);
return run(orders_source.ObserveOn(ThreadPoolScheduler.Instance));
}
public static IDisposable run(IObservable<Update<OrderRecordImmutable, uint>> orders_source) {
object locker = new object();
var account_order_map = new Dictionary<AccountKey, Dictionary<OrderKey, OrderRecordImmutable>>();
return orders_source.Subscribe(on_update);
void on_order_added(OrderRecordImmutable new_order) {
if (!account_order_map.TryGetValue(new_order.Def.AccountId, out var account_orders)) {
// this is the first order for this account.
// initialize the account orders dictionary, and add it to our data.
account_orders = new Dictionary<AccountKey, OrderRecordImmutable>();
account_order_map.Add(new_order.Def.AccountId, account_orders);
}
_print_order_info(new_order, $"observed for the first time.");
// now add the order to account_orders
account_orders.Add(new_order.Key, new_order);
}
void on_order_changed(Change<OrderRecordImmutable> change) {
// ch.New and ch.Old have the same key.
Debug.Assert(change.New.Key == change.Old.Key);
var key = change.New.Key;
// detect fills
if (change.New.State.FilledQuantity != change.Old.State.FilledQuantity) {
if (change.New.State.FilledQuantity > change.New.State.Quantity) {
// order has been overfilled
var num_over_fills = change.New.State.FilledQuantity - change.New.State.Quantity;
_print_order_info(change.New, $"overfilled by {num_over_fills}.");
} else if (change.New.State.FilledQuantity == change.New.State.Quantity) {
// order has been filled to completion
_print_order_info(change.New, "filled to completion");
} else if (change.New.State.Status == OrderStatus.Working) {
// order is still working.
var working_qty = change.New.State.Quantity - change.New.State.FilledQuantity;
_print_order_info(change.New, $"still working {working_qty}");
} else {
// order has stopped working prematurely (perhaps it was canceled)
_print_order_info(change.New, $"is inactive");
}
}
// Account will not change, so our dictionary will definitely have a key for this account.
Debug.Assert(account_order_map.ContainsKey(change.New.Def.AccountId));
var account_orders = account_order_map[change.New.Def.AccountId];
// The value for this key in account_orders should equal ch.Old.
Debug.Assert(account_orders[key].Equals(change.Old));
// update account orders with the changed order.
account_orders[key] = change.New;
}
void on_order_removed(OrderRecordImmutable old_val) {
// An entry for this order should be present in our data,
// as it is in the 'removed' items.
// Assert this is the case to illustrate the point.
Debug.Assert(account_order_map.ContainsKey(old_val.Def.AccountId));
var account_orders = account_order_map[old_val.Def.AccountId];
// the removed value should match the one currently in our data.
// Assert this is the case, then remove the order.
Debug.Assert(account_orders[old_val.Key].Equals(old_val));
account_orders.Remove(old_val.Key);
}
void on_update(Update<OrderRecordImmutable, OrderKey> update) {
lock (locker) {
foreach (OrderRecordImmutable new_order in update.added) {
on_order_added(new_order);
}
foreach (Change<OrderRecordImmutable> change in update.changes) {
on_order_changed(change);
}
foreach (OrderRecordImmutable old_order in update.removed) {
on_order_removed(old_order);
}
}
}
}
static void _print_order_info(OrderRecordImmutable order, string message) {
Console.WriteLine($"Order {order.Key} ({order.State.Status}, filled {order.State.FilledQuantity}/{order.State.Quantity}): {message}.");
}
}
}
@@ -0,0 +1,112 @@
using bts.api.core;
using bts.api.position;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
namespace bts.api.csharp.watch {
/// <summary>
/// A pair of security, and account id,
/// which uniquely identifies each PositionUnit.
/// </summary>
readonly struct PositionKey {
public PositionKey(uint security_id, uint account_id) {
this.security_id = security_id;
this.account_id = account_id;
}
public uint security_id { get; }
public uint account_id { get; }
public static PositionKey from_position(PositionUnitImmutable p) =>
new PositionKey(p.SecurityId, p.AccountId);
public override string ToString() =>
$"Sec {security_id}, Acc {account_id}";
}
static class WatchPositions {
/// <summary>
/// Uses the <see cref="PositionService"/> api to monitor positions
/// for every account/security pair, and organize them into a Dictionary.
/// </summary>
public static IDisposable run(PositionService position_service, Func<PositionUnitImmutable, bool> predicate) {
/// 'PositionService' has 2 methods which can be used to subscribe to position updates.
/// -------------------------------------------------------------------------------------
/// 1) <see cref="PositionService.subscribe_position_data(string)"/>.
/// 2) <see cref="PositionService.subscribei_position_data(string)"/>.
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
/// --------------------------------------------------------------------------------------
return run(position_service.subscribei_position_data().Filter(predicate).ObserveOn(ThreadPoolScheduler.Instance));
}
public static IDisposable run(IObservable<Update<PositionUnitImmutable, int>> position_source) {
var data = new Dictionary<PositionKey, PositionUnitImmutable>();
return position_source.Subscribe(on_update);
void on_position_added(PositionUnitImmutable new_val) {
var k = PositionKey.from_position(new_val);
// this entry should NOT be present in our _data, as it is in the 'added' items.
// Assert this is the case to illustrate the point.
Debug.Assert(!data.ContainsKey(k));
print_position_info(k, $"initial position received. Day: Long {new_val.DayLongPos}, Short {new_val.DayShortPos}, Net {new_val.DayLongPos - new_val.DayShortPos}.");
// add the new position unit to _data
data.Add(k, new_val);
}
void on_position_changed(Change<PositionUnitImmutable> change) {
var k = PositionKey.from_position(change.New);
// ch.New and ch.Old have the same key (i.e., the Account/Security pair),
Debug.Assert(change.New.AccountId == change.Old.AccountId);
Debug.Assert(change.New.SecurityId == change.Old.SecurityId);
// of course, the value for this key in our dictionary should equal ch.Old.
Debug.Assert(data[k].Equals(change.Old));
// Display changes to the day position in the console window.
// 1) check if the day long changed
if (change.New.DayLongPos != change.Old.DayLongPos) {
print_position_info(k, $"day long changed from {change.Old.DayLongPos} to {change.New.DayLongPos}.");
}
// 2) check if day short changed
if (change.New.DayShortPos != change.Old.DayShortPos) {
print_position_info(k, $"day short changed from {change.Old.DayShortPos} to {change.New.DayShortPos}.");
}
// 3) check if net day position changed
var new_net_day_pos = change.New.DayLongPos - change.New.DayShortPos;
var old_net_day_pos = change.Old.DayLongPos - change.Old.DayShortPos;
if (new_net_day_pos != old_net_day_pos) {
print_position_info(k, $"day net changed from {old_net_day_pos} to {new_net_day_pos}.");
}
// update _data with the changed position.
data[k] = change.New;
}
void on_position_removed(PositionUnitImmutable old_val) {
var k = PositionKey.from_position(old_val);
// this entry should be present in our _data, as it is in the 'removed' items.
// Assert this is the case to illustrate the point.
Debug.Assert(data.ContainsKey(k));
// this should match our data. Assert this is the case.
Debug.Assert(data[k].Equals(old_val));
data.Remove(k);
}
void on_update(Update<PositionUnitImmutable, int> update) {
foreach (PositionUnitImmutable p in update.added) {
on_position_added(p);
}
foreach (Change<PositionUnitImmutable> ch in update.changes) {
on_position_changed(ch);
}
foreach (PositionUnitImmutable p in update.removed) {
on_position_removed(p);
}
}
void print_position_info(PositionKey position_key, string message) {
Console.WriteLine($"Position {position_key}: {message}.");
}
}
}
}
@@ -0,0 +1,22 @@
using bts.api.core;
using bts.api.csharp.utils;
using System;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
namespace bts.api.csharp.watch {
static class WatchSecurities {
public static IDisposable run(MarketDataService market_data_service, Func<SecurityImmutable, bool> predicate) {
// watch securities
var securities = market_data_service.subscribei_securities().Table();
if (predicate != null)
securities = securities.Filter(predicate);
return securities
.ObserveOn(ThreadPoolScheduler.Instance)
.Subscribe(upd => {
if (!upd.is_empty())
UpdateUtils.print_incremental_changes(upd, sec => sec.Id, null, "Securities");
});
}
}
}
@@ -0,0 +1,48 @@
using bts.api.core;
using System;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using SymbolTableGroup = bts.api.core.TableUpdateGrouping<bts.api.SecurityImmutable, uint, string>;
namespace bts.api.csharp.watch {
static class WatchSymbols {
/// <summary>
/// Observes securities via <see cref="MarketDataService.subscribei_securities(string)"/>, and prints newly received symbols to the console.
/// </summary>
/// <returns>An IDisposable to dispose the subscription to the securities source.</returns>
public static IDisposable run(MarketDataService market_data_service) {
return run(market_data_service.subscribei_securities().Table());
}
/// <summary>
/// Observes a security update sequence, and prints newly received symbols to the console.
/// </summary>
/// <returns>An IDisposable to dispose the subscription to the securities source.</returns>
public static IDisposable run(IObservable<TableUpdate<SecurityImmutable, uint>> securities_source) {
ushort update_count = 0;
object locker = new object();
return securities_source.GroupTable(sec => sec.Id, sec => sec.Symbol)
.ObserveOn(ThreadPoolScheduler.Instance)
.Subscribe(on_update);
void on_update(TableUpdate<SymbolTableGroup, string> group_update) {
lock (locker) {
var num_new_securities = group_update.added.Concat(group_update.changed).Sum(grp => grp.table.added.Count());
Console.WriteLine(
$"Security update number {++update_count} provided {num_new_securities} new securities " +
$"on {group_update.added.Count()} new symbols.");
var new_symbols = group_update.added
.Select(table_group => table_group.group_key)
.OrderBy(sym => sym);
foreach (var new_symbol in new_symbols) {
Console.WriteLine("\t" + new_symbol);
}
}
}
}
}
}
+1 -1
View File
@@ -1 +1 @@
set(sources algo/AlgoDefinitionServiceServer.cpp;cmds/AdminReport.cpp;cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/FIXSim.cpp;cmds/Help.cpp;cmds/KeyCode.cpp;cmds/Md.cpp;cmds/ParseHistoricalFIX.cpp;cmds/PrintHedgeOrders.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/TestCeloxica.cpp;cmds/Web.cpp;dspark/mirage/quick_queue/quick_queue.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/MultipartFormDecoder.cpp;http/WebServer.cpp;http/tests/MultiPartFormTests.cpp;main.cpp;md/ChannelVolume.cpp;md/MarketDataAuxiliaryServiceServer.cpp;md/VolumeAtPriceState.cpp;md/platforms/IMDPlatform.cpp;md/platforms/celoxica/CXListener.cpp;md/platforms/celoxica/CXMain.cpp;md/platforms/celoxica/tests/cx_tests.cpp;md/platforms/ice_onixs/ICEListener.cpp;md/platforms/ice_onixs/ICEMain.cpp;md/platforms/ice_onixs/tests/ice_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;position/IPositionObserver.cpp;position/MarginServiceCore.cpp;position/MarginServiceServer.cpp;position/PositionAggregator.cpp;position/PositionMap.cpp;position/PositionServicePNLHelper.cpp;position/PositionServiceServer.cpp;position/import/BasePositionServiceServer.cpp;position/import/DownloadCommonStateMachine.cpp;position/import/DownloadWindow.cpp;position/import/MoneyFileServiceServer.cpp;position/import/parsing/gmi_parser.cpp;position/import/parsing/money_parser.cpp;position/import/parsing/parser_common.cpp;position/tests/PositionTests.cpp;position/tests/position_tests_internal.cpp;subprocess/SubProcessRunner.cpp;support/ErrorReportService.cpp;support/ShellLatencyServer.cpp;user/AdminServiceServer.cpp;user/AuthServiceServer.cpp;user/tests/admin_service_tests.cpp)
set(sources algo/AlgoDefinitionServiceServer.cpp;cmds/AdminReport.cpp;cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/FIXSim.cpp;cmds/Help.cpp;cmds/KeyCode.cpp;cmds/Md.cpp;cmds/ParseHistoricalFIX.cpp;cmds/PrintHedgeOrders.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/Web.cpp;dspark/mirage/quick_queue/quick_queue.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/MultipartFormDecoder.cpp;http/WebServer.cpp;http/tests/MultiPartFormTests.cpp;main.cpp;md/ChannelVolume.cpp;md/MarketDataAuxiliaryServiceServer.cpp;md/VolumeAtPriceState.cpp;md/platforms/IMDPlatform.cpp;md/platforms/ice_onixs/ICEListener.cpp;md/platforms/ice_onixs/ICEMain.cpp;md/platforms/ice_onixs/tests/ice_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;position/IPositionObserver.cpp;position/MarginServiceCore.cpp;position/MarginServiceServer.cpp;position/PositionAggregator.cpp;position/PositionMap.cpp;position/PositionServicePNLHelper.cpp;position/PositionServiceServer.cpp;position/import/BasePositionServiceServer.cpp;position/import/DownloadCommonStateMachine.cpp;position/import/DownloadWindow.cpp;position/import/MoneyFileServiceServer.cpp;position/import/parsing/gmi_parser.cpp;position/import/parsing/money_parser.cpp;position/import/parsing/parser_common.cpp;position/tests/PositionTests.cpp;position/tests/position_tests_internal.cpp;subprocess/SubProcessRunner.cpp;support/ErrorReportService.cpp;support/ShellLatencyServer.cpp;user/AdminServiceServer.cpp;user/AuthServiceServer.cpp;user/tests/admin_service_tests.cpp)
-26
View File
@@ -1,26 +0,0 @@
#include "TestCeloxica.h"
#include "server/sparkd/md/platforms/celoxica/CXMain.h"
#include "server/sparkd/md/platforms/celoxica/CXListener.h"
namespace bts
{
namespace cmd
{
int TestCeloxica::execute(const std::vector<std::string>&)
{
bts::md::CXMain main;
main.initialize();
return 0;
}
std::string TestCeloxica::help()
{
return "\t test BTS <> Celoxica integration \n";
}
}
}
-20
View File
@@ -1,20 +0,0 @@
#pragma once
#include "ICmd.h"
namespace bts
{
namespace cmd
{
class TestCeloxica : public ICmd
{
public:
int execute(std::vector<std::string> const &args);
std::string help();
};
}
}
-2
View File
@@ -19,7 +19,6 @@
#include "server/sparkd/cmds/RunSim.h"
#include "server/sparkd/cmds/Span.h"
#include "server/sparkd/cmds/Test.h"
#include "server/sparkd/cmds/TestCeloxica.h"
#include "server/sparkd/cmds/Web.h"
using namespace bts;
@@ -32,7 +31,6 @@ int main(int argc, char **argv) {
REGISTER(Help);
REGISTER(Run);
REGISTER(Md);
REGISTER(TestCeloxica);
REGISTER(ConfigCheck);
REGISTER(KeyCode);
REGISTER(RunSim);
@@ -1,88 +0,0 @@
#include "CXListener.h"
#include "lib/utils/defer.h"
#include "lib/utils/CallTimer.h"
#include "lib/utils/helper_macros.h"
#include <tuple>
namespace celoxica { namespace gmac {} }
#include "celoxica.h"
using namespace celoxica::gmac;
using namespace std;
namespace bts
{
namespace md
{
CXListener::CXListener(CXSecurityIndex& index, MDProducer& producer, SecurityIDRecorder& recorder,
co2::SettlementRecorder& settles, std::vector<MDProducerChannel> channels,
utils::Dispatcher& disp) :
_index(index),
_producer(producer),
_packet_arrived(),
_latest_sequence(0),
_security_recorder(recorder),
_settlement_recorder(settles),
_symbol_filter_storage(),
_channels(channels),
_background(disp),
_security_recorder_input_timer(disp, 900, [&]() { dequeue_securities(); }),
_security_recorder_input(),
_concurrency_monitor(0),
_book_updates()
{
_book_updates.reserve(32);
_security_recorder_input_timer.start();
}
CXListener::~CXListener()
{
_security_recorder_input_timer.stop();
}
void CXListener::add_software_filter(const std::string& symbol)
{
_symbol_filter_storage.push_back(symbol);
//_symbol_filter.clear();
//for (const auto& str : _symbol_filter_storage) {
// _symbol_filter.insert(onixs::StringRef(str));
//}
}
void CXListener::on_GMACMessageOrderDepthV3(const GMACMessageOrderDepthV3*)
{
}
void CXListener::dequeue_securities()
{
}
void CXListener::flush_books()
{
// This is called at the end of the packet
// Write all book updates to shared memory and reset for the next packet
//for (auto& entry : _book_updates) {
// // bbo first
// uint16_t sequence = (_latest_sequence & 0xFFFF);
// entry.handle.update_bbo(_packet_arrived, entry.bids[0], entry.asks[0], sequence);
// // Perf Note: the remainder of the book update could be done (carefully) with a memcpy
// for (size_t x = 1; x < COCONUT_MAX_DEPTH; ++x) {
// entry.handle.update_bid(_packet_arrived, entry.bids[x], x);
// entry.handle.update_ask(_packet_arrived, entry.asks[x], x);
// }
//}
//// Reset pending updates
//_book_updates.clear();
}
}
md::CXListener::BookData::BookData(MDProducerHandle & handle) : handle(handle), bids(), asks()
{
}
}//namespace
@@ -1,61 +0,0 @@
#pragma once
#include "lib/utils/bts_no_copy.h"
#include "lib/md/coconut2.h"
#include "lib/utils/Dispatcher.h"
#include "server/sparkd/md/platforms/celoxica/CXSecurityIndex.h"
#include <tbb/concurrent_queue.h>
#include <vector>
#include <atomic>
namespace bts
{
namespace md
{
struct GMACMessageOrderDepthV3;
class CXListener
{
public:
CXListener(CXSecurityIndex&, MDProducer&, SecurityIDRecorder&,
co2::SettlementRecorder&, std::vector<MDProducerChannel>, utils::Dispatcher&);
~CXListener();
void add_software_filter(const std::string& symbol);
void on_GMACMessageOrderDepthV3(const GMACMessageOrderDepthV3*);
private:
void flush_books();
void dequeue_securities();
private:
BTS_NO_COPY(CXListener);
CXSecurityIndex& _index;
MDProducer& _producer;
utils::TimeStamp _packet_arrived;
uint64_t _latest_sequence;
SecurityIDRecorder& _security_recorder;
co2::SettlementRecorder& _settlement_recorder;
std::vector<std::string> _symbol_filter_storage;
std::vector<MDProducerChannel> _channels;
utils::Dispatcher& _background;
utils::DispatcherTimer _security_recorder_input_timer;
tbb::concurrent_queue<CXSecurityData*> _security_recorder_input;
std::atomic<int> _concurrency_monitor;
struct BookData {
BookData(MDProducerHandle& handle);
MDProducerHandle handle;
PriceSize bids[10];
PriceSize asks[10];
};
std::vector<BookData> _book_updates;
};
}}//namespace
@@ -1,83 +0,0 @@
#include "CXMain.h"
#include "lib/md/cme/MDP3Utils.h"
#include "lib/config/config.h"
#include "lib/md/coconut2.h"
#include "lib/os/os.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/proto_helper.h"
#include "lib/utils/helper_macros.h"
#include "lib/config/protos/MarketData.pb.h"
#include "lib/config/protos/system.pb.h"
namespace celoxica { namespace gmac {} }
#include "celoxica.h"
extern "C"
{
#define __retcode_start__
#define __retcode_end__
}
#include <memory>
#include <set>
using namespace std;
using namespace celoxica::gmac;
namespace bts
{
namespace md
{
void run_cx_server(UserSymbol* psyms, size_t symcount)
{
CXMain main;
vector<UserSymbol> symbols;
for(size_t x = 0; x < symcount; ++x) {
symbols.push_back(psyms[x]);
}
main.run(symbols);
}
CXMain::CXMain() :
_producer(co2::PLATFORM_CELOXICA),
_security_recorder({}),
_settlement_recorder(co2::PLATFORM_CELOXICA),
_listeners()
{
}
CXMain::~CXMain() {}
void CXMain::initialize()
{
//celoxica::gmac::GMACIdentify(stderr);
//const auto& options = config::get<CMEOptions>();
}
void CXMain::run(std::vector<UserSymbol>& symbols)
{
bts::md::await_cme_config();
initialize();
parse_channels(symbols);
// const auto& channels = parse_channels(symbols);
}
std::unique_ptr<CXListener> CXMain::prepare_listener(const std::string& channel, const CMEChannel&)
{
UNUSED(channel);
return std::unique_ptr<CXListener>();
}
}
}
@@ -1,49 +0,0 @@
#pragma once
#include "lib/utils/bts_no_copy.h"
#include "lib/md/coconut2.h"
#include "server/sparkd/md/platforms/celoxica/CXListener.h"
#include "server/sparkd/md/platforms/celoxica/CXSecurityIndex.h"
#include <memory>
#include <vector>
#include <map>
namespace bts
{
namespace md
{
void run_cx_server(UserSymbol* sym, size_t symcount);
struct CMEChannel;
class CXListener;
/*
* The CX provider is very simple. 1 CXMain maps to 1 Celoxica Queue, which maps to 1 thread and one bound CPU.
* Control starts in CXMain which starts the thread, binds to a cpu, provides a (currently unused) list of all the symbols we need
* prepare_handler configures an exchange channel to be owned by this queue, and prepare_listener sets up thing that sees
* all callbacks which maps the exchange id to the MDProducerHandle and writes to shared mem
*/
class CXMain
{
public:
CXMain();
~CXMain();
void run(std::vector<UserSymbol>&); //call initialize, start the pump
void initialize(); //set up channels, etc
std::unique_ptr<CXListener> prepare_listener(const std::string& channel, const CMEChannel&);
private:
BTS_NO_COPY(CXMain);
CXSecurityIndex _index;
MDProducer _producer;
SecurityIDRecorder _security_recorder;
co2::SettlementRecorder _settlement_recorder;
std::vector<std::unique_ptr<CXListener>> _listeners;
};
}}//namespace
@@ -1,21 +0,0 @@
#pragma once
#include "lib/md/SecurityIndex.h"
#include <string>
namespace bts
{
namespace md
{
struct CXSecurityData
{
CXSecurityData(uint32_t id, MDProducerHandle& h) : id(id), handle(h) {}
uint32_t id; // exchange id
MDProducerHandle handle;
};
using CXSecurityIndex = ConcurrentSecurityIndex<uint32_t, CXSecurityData>;
}}//namespace
@@ -1,15 +0,0 @@
#pragma once
namespace celoxica
{
namespace gmac
{
#include "celoxica/gmac.h"
#include "celoxica/gmac_template_v3.h"
#include "celoxica/gmac_info.h"
}
}
@@ -1,210 +0,0 @@
// #include "catch/catch.hpp"
// #include "lib/config/config.h"
// #include "lib/config/protos/MarketData.pb.h"
// #include <vector>
// #include <string>
// #include <functional>
// #include <stdarg.h>
// #include "../MDP3Utils.h"
// #include "../onixs.h"
// using namespace bts::md;
// using namespace std;
// namespace bts {
// namespace md {
// map<string, onixs::FeedEngineSettings> create_feed_engine_settings(const std::vector<CMEChannel>& channels);
// const CMEOptions::CMEChannel channel_settings_for(const string& id);
// namespace test {
// vector<CMEChannel> test_channels(int count, ...) {
// // Doing this the C way because I still haven't updated VS
// vector<CMEChannel> result;
// va_list ap;
// va_start(ap, count);
// for (int x = 0; x < count; ++x) {
// const char* val = va_arg(ap, char*);
// CMEChannel temp;
// temp.id = val;
// result.push_back(temp);
// }
// va_end(ap);
// return result;
// }
// TEST_CASE("mdp3_tests","[md]") {
// SECTION("test_defaults") {
// config::internal::initialize_for_test("CMEOptions {}");
// vector<CMEChannel> channels = test_channels(1, "310");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// channels = test_channels(2, "310", "311");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// channels = test_channels(4, "310", "311", "400", "412");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// REQUIRE(!channel_settings_for("310").has_feed_engine());
// REQUIRE(!channel_settings_for("311").has_feed_engine());
// REQUIRE(!channel_settings_for("315").has_feed_engine());
// }
// SECTION("test_segregate_futures") {
// config::internal::initialize_for_test(
// "CMEOptions {\n"
// " value {\n"
// " feed_engine {\n"
// " name: 'default'\n"
// " }\n"
// " feed_engine {\n"
// " name: 'futures'\n"
// " num_threads: 4\n"
// " }\n"
// " # Futures\n"
// " channel {\n"
// " id: '310'\n"
// " id: '312'\n"
// " feed_engine: 'futures'\n"
// " }\n"
// " # Options\n"
// " channel {\n"
// " id: '311'\n"
// " id: '313'\n"
// " id: '315'\n"
// " feed_engine: 'default'\n"
// " }\n"
// " }\n"
// "}");
// vector<CMEChannel> channels = test_channels(1, "310");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// REQUIRE(result.begin()->first == "futures");
// REQUIRE(result.begin()->second.threadsCount() == 4);
// channels = test_channels(1, "311");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// REQUIRE(result.begin()->first == "default");
// REQUIRE(result.begin()->second.threadsCount() == 1);
// channels = test_channels(2, "311", "310");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 2);
// REQUIRE(result["default"].threadsCount() == 1);
// REQUIRE(result["futures"].threadsCount() == 4);
// // Test channels not listed in config
// channels = test_channels(2, "317", "318");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// }
// SECTION("test_no_default") {
// config::internal::initialize_for_test(
// "CMEOptions {\n"
// " value {\n"
// " feed_engine {\n"
// " name: 'futures'\n"
// " num_threads: 4\n"
// " }\n"
// " # any channel not listed will use the default feed engine\n"
// " # Futures\n"
// " channel {\n"
// " id: '310'\n"
// " id: '312'\n"
// " feed_engine: 'futures'\n"
// " }\n"
// " # Options\n"
// " channel {\n"
// " id: '311'\n"
// " id: '313'\n"
// " id: '315'\n"
// " use_tcp_replay: false\n"
// " }\n"
// " }\n"
// "}");
// vector<CMEChannel> channels = test_channels(1, "310");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// REQUIRE(result.begin()->first == "futures");
// REQUIRE(result.begin()->second.threadsCount() == 4);
// channels = test_channels(1, "311");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// channels = test_channels(2, "311", "310");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// REQUIRE(result["futures"].threadsCount() == 4);
// REQUIRE(channel_settings_for("310").use_tcp_replay() == true);
// REQUIRE(channel_settings_for("311").use_tcp_replay() == false);
// REQUIRE(channel_settings_for("315").use_tcp_replay() == false);
// }
// SECTION("test_one_thread_each") {
// config::internal::initialize_for_test(
// "CMEOptions {\n"
// " value {\n"
// " channel {\n"
// " id: '*'\n"
// " }\n"
// " }\n"
// "}\n");
// vector<CMEChannel> channels = test_channels(1, "310", "311");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// const auto& settings = channel_settings_for("311");
// REQUIRE(!settings.has_feed_engine());
// }
// SECTION("test_common_futures_thread") {
// config::internal::initialize_for_test(
// "CMEOptions {\n"
// " value {\n"
// " feed_engine {\n"
// " name: 'futures'\n"
// " num_threads: 4\n"
// " }\n"
// " channel {\n"
// " id: '310'\n"
// " feed_engine: 'futures'\n"
// " udp_socket_buffer_size: 1234\n"
// " }\n"
// " channel {\n"
// " id: '*'\n"
// " }\n"
// " }\n"
// "}\n");
// vector<CMEChannel> channels = test_channels(6, "310", "311", "315", "100", "10", "42");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// result = create_feed_engine_settings(test_channels(1, "315"));
// REQUIRE(result.size() == 0);
// REQUIRE(!channel_settings_for("311").has_feed_engine());
// REQUIRE(channel_settings_for("310").has_feed_engine());
// REQUIRE(channel_settings_for("310").udp_socket_buffer_size() == 1234);
// REQUIRE(channel_settings_for("311").has_udp_socket_buffer_size() == false);
// }
// }
// }}}//namespace
+1 -10
View File
@@ -2,11 +2,6 @@
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export PATH="$PATH:/root/.dotnet/tools"
# install npx
if ! [ -x "$(command -v npx)" ]; then
npm install -g npx
fi
export OUT=${BUILD_DIR:-../../../build}/web
MODE=manual
case "$OUT" in
@@ -39,9 +34,6 @@ PROTOC=../../../externals/linux/bin/protoc
ORANGE='\033[0;33m'
NOCOLOR='\033[0m'
npm install
# Run pbjs to produce protobuf module
echo -e \* ${ORANGE} pbjs ${NOCOLOR}
npx --no-install pbjs -t json-module -w commonjs -o $OUT/api/proto.js $PROTO_LIST
@@ -79,11 +71,10 @@ if [ "$FULL" == "1" ]; then
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
echo -e \* ${ORANGE} fable ${NOCOLOR}
cd ../ui/
npm install
cd src
mkdir -p $OUT/ui/
cp -r ../public/* $OUT/ui/
dotnet restore
dotnet restore -s ../../../../externals/nuget/packages
if [ "$MODE" == "release" ]; then
dotnet fable webpack -- --env.BUILD_PATH=$OUT -p
else
-22
View File
@@ -1,22 +0,0 @@
cmake_minimum_required(VERSION 3.5)
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libbase.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libutils.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libfix_lib.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclxlog.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libade_version.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libade.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclx.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libgmac_version.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libgmac.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libcputimestamp.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclxconfig.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libconfigfile.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libac.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/librc.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/liblldt.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libregister.so")
file(COPY ${CELOXICA_LIB} DESTINATION ${CMAKE_BINARY_DIR}/src/server/sparkd)
+1 -1
View File
@@ -7,7 +7,7 @@ if not exist build\src\lib\libspark.vcxproj (
)
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\Common7\Tools\VsDevCmd.bat"
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore spark_client.sln /p:Configuration=release
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln /p:Configuration=release
IF %ERRORLEVEL% NEQ 0 GOTO :ERROR_AND_END
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary spark_client.sln /p:Configuration=release
+3 -4
View File
@@ -1,9 +1,8 @@
#!/bin/bash
BUILD=clang_debug
BUILD_LOC=/home/ppinto/temp/spark_build
if [ -f /.dockerenv ]; then
cd $BUILD_LOC/$BUILD && ninja
(cd `readlink -f .` && ninja)
else
cid=`docker ps | grep -P "emacs_ide|bluetradingsystems/linux_docker" | awk '{print $1}'`
docker exec $cid /bin/bash -c "cd $BUILD_LOC/$BUILD && ninja"
docker exec $cid /bin/bash -c "(cd `readlink -f .` && ninja)"
fi
+2 -2
View File
@@ -2,6 +2,6 @@
setlocal
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\Tools\VsDevCmd.bat" > nul
cd /d %~dp0/../
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore spark_client.sln
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /p:Configuration=release /t:restore spark_client.sln
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /p:Configuration=release /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln
+5
View File
@@ -4,6 +4,11 @@ SRC_ROOT=$DIR/../
mkdir -p $SRC_ROOT/build/
BUILD_ROOT=`readlink -f $SRC_ROOT/build`
rm -f $SRC_ROOT/src/web/ui/node_modules
rm -f $SRC_ROOT/src/web/api/node_modules
ln -s $SRC_ROOT/externals/npm/node_modules $SRC_ROOT/src/web/ui/node_modules
ln -s $SRC_ROOT/externals/npm/node_modules $SRC_ROOT/src/web/api/node_modules
rm -rf $BUILD_ROOT/clang_debug
mkdir $BUILD_ROOT/clang_debug
cd $BUILD_ROOT/clang_debug