Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7cee30511 | ||
|
|
98690ee747 | ||
|
|
3e14d83dde | ||
|
|
746622161c | ||
|
|
c064cdd215 | ||
|
|
acce1e3f7d | ||
|
|
9bdf084838 | ||
|
|
5132f81bc1 | ||
|
|
ab797a311e | ||
|
|
d032b23841 | ||
|
|
8c68a66ef1 | ||
|
|
d617d1597c | ||
|
|
625f76e1f4 | ||
|
|
ccff3ee160 | ||
|
|
1b1c8a7c1d | ||
|
|
5206a378ff | ||
|
|
7d2047d7ce | ||
|
|
8a43a4d157 | ||
|
|
230e9e525f | ||
|
|
034318db5e | ||
|
|
0758dd6f46 | ||
|
|
83f9f3375c | ||
|
|
98a6616804 | ||
|
|
b46f811cb5 | ||
|
|
7cf37bb080 | ||
|
|
6d918c0962 | ||
|
|
7cd052979e | ||
|
|
3b3d44d6f6 | ||
|
|
2b2eaac979 | ||
|
|
5b5274ab5f | ||
|
|
484ad656f9 | ||
|
|
0e92ee15f4 | ||
|
|
2d5211ac9b | ||
|
|
e62d116a44 |
No files matched your search
@@ -173,7 +173,8 @@ namespace mbts.spark {
|
||||
}
|
||||
_is_setting_price_mults = false;
|
||||
};
|
||||
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
|
||||
//_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
|
||||
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => true), true);
|
||||
} else {
|
||||
_can_add_leg = backing(nameof(can_add_leg), false);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace bts.spark {
|
||||
(var security, var spec) = get_security(neworder.Def.SecurityKey);
|
||||
//
|
||||
// description
|
||||
var mod_desc = modified_description(oldorder, neworder, spec.Value);
|
||||
var mod_desc = modified_description(oldorder, neworder, spec);
|
||||
if (!string.IsNullOrEmpty(mod_desc)) {
|
||||
log_status(ActivityLogCategory.modify, security, mod_desc);
|
||||
}
|
||||
|
||||
@@ -197,10 +197,6 @@ namespace mbts.spark
|
||||
selected_securities = secs.Select(s => s.Id).ToList();
|
||||
ok_button_command.TryRaiseCanExecuteChanged();
|
||||
_compose_criteria_clause_name();
|
||||
|
||||
// _security_in_criteria is our fun to be returned to our callers
|
||||
// test on click -- verify our func works
|
||||
//var results = all_securities.Select(all => all.Value.security).Where(x => _security_in_criteria(x)).ToList();
|
||||
}
|
||||
|
||||
private void on_securities_changed(IReadOnlyDictionary<uint, SecurityMD> changed_securities) {
|
||||
@@ -212,10 +208,6 @@ namespace mbts.spark
|
||||
all_securities = changed_securities;
|
||||
}
|
||||
|
||||
securities.sync_with(all_securities
|
||||
.Select(entry => entry.Value.security)
|
||||
);
|
||||
|
||||
_update_exchanges();
|
||||
_update_security_types();
|
||||
_update_symbols();
|
||||
@@ -327,7 +319,6 @@ namespace mbts.spark
|
||||
.Select(s => s.Value.security.Symbol)
|
||||
.Distinct()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private void _update_securities() {
|
||||
|
||||
@@ -76,6 +76,10 @@ message CMEOptions
|
||||
repeated uint32 affinity = 10;
|
||||
optional uint32 udp_socket_buffer_size = 11 [default = 33554432]; //32mb
|
||||
}
|
||||
enum BookType {
|
||||
BOOKTYPE_CONSOLIDATED = 0;
|
||||
BOOKTYPE_DIRECT = 1;
|
||||
}
|
||||
message CMEChannel {
|
||||
repeated string id = 2;
|
||||
optional string feed_engine = 3 [default = "default"];
|
||||
@@ -98,6 +102,7 @@ message CMEOptions
|
||||
optional bool join_snapshot_on_gap = 32 [default = false];
|
||||
|
||||
optional bool load_security_db = 33 [default = true];
|
||||
optional BookType book_type = 34 [default = BOOKTYPE_CONSOLIDATED];
|
||||
|
||||
}
|
||||
optional string onixs_license = 1 [default = "{default}"];
|
||||
|
||||
@@ -199,9 +199,18 @@ void SecurityIDRecorder::bulk_insert(const std::vector<const api::Security*>& da
|
||||
if (data.size() == 0) return;
|
||||
sqlite3_exec_nofail(_self->db.get(), "BEGIN TRANSACTION");
|
||||
std::string descriptor;
|
||||
for (const api::Security* sec : data) {
|
||||
descriptor = security_descriptor(*sec);
|
||||
insert(prefix + descriptor, securitytype_of(*sec));
|
||||
{
|
||||
auto insert_stmt = prepare_query(_self->db, "INSERT INTO security_keys (rowid, descriptor, type) values (?, ?, ?)");
|
||||
for (const api::Security* sec : data) {
|
||||
descriptor = prefix + security_descriptor(*sec);
|
||||
auto type = securitytype_of(*sec);
|
||||
|
||||
sqlite3_bind_int(insert_stmt.get(), 1, sec->id());
|
||||
sqlite3_bind_text(insert_stmt.get(), 2, descriptor.c_str(), descriptor.size(), SQLITE_STATIC);
|
||||
sqlite3_bind_int(insert_stmt.get(), 3, (int)type);
|
||||
sqlite3_step(insert_stmt.get());
|
||||
sqlite3_reset(insert_stmt.get());
|
||||
}
|
||||
}
|
||||
sqlite3_exec_nofail(_self->db.get(), "COMMIT TRANSACTION");
|
||||
}
|
||||
|
||||
@@ -121,9 +121,6 @@ void record_security_keys(md::SecurityIDRecorder& idrec, const co2::MDReply& rep
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sort by id only actually works if the recording was done on a fresh machine
|
||||
std::sort(data.begin(), data.end(), [](const Security* a, const Security* b) { return a->id() < b->id(); });
|
||||
// We could insert garbage until data[0]->id()
|
||||
idrec.bulk_insert(data, "REPLAY: ");
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/quickbus/quickbus.h"
|
||||
#include "lib/utils/Dispatcher.h"
|
||||
|
||||
#include "lib/md/SyntheticSecurityService.h"
|
||||
#include "server/sparkd/exchange_sim/ExchangeSim.h"
|
||||
@@ -53,6 +54,11 @@ int Run::execute(std::vector<std::string> const &) {
|
||||
orderman::OrderServiceServer order_service_server(sc, synthetic_service);
|
||||
support::ErrorReportServer error_report_service(sc);
|
||||
admin::AdminServiceServer admin_service(sc);
|
||||
|
||||
auto bg_health = std::make_shared<metrics::IntCounterMetric>("spark_background_thread");
|
||||
utils::DispatcherTimer timer(utils::Dispatcher::background(), 5000, [=]() { bg_health->increment(); });
|
||||
timer.start();
|
||||
stats.publish(bg_health);
|
||||
|
||||
std::thread([]() {
|
||||
os::set_thread_name("ExchangeSim");
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "MDP3Listener.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include <vector>
|
||||
#include "server/sparkd/tracing/bts_trace.h"
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace bts::co2;
|
||||
@@ -84,7 +84,8 @@ MDP3Listener::MDP3Listener(
|
||||
MDProducer& producer,
|
||||
SecurityIDRecorder& recorder,
|
||||
co2::SettlementRecorder& ,
|
||||
std::vector<MDProducerChannel> channels) :
|
||||
const std::vector<MDProducerChannel> channels,
|
||||
CMEOptions::BookType bt) :
|
||||
_index(index),
|
||||
_producer(producer),
|
||||
_packet_arrived(),
|
||||
@@ -94,6 +95,7 @@ MDP3Listener::MDP3Listener(
|
||||
_symbol_filter_storage(),
|
||||
_symbol_filter(),
|
||||
_channels(channels),
|
||||
_booktype(bt),
|
||||
_concurrency_monitor(0),
|
||||
_book_updates()
|
||||
{
|
||||
@@ -188,11 +190,6 @@ double double_of_price(const Price& price, int8_t display_factor) {
|
||||
return (price.mantissa() * pow10(price.exponent())) * pow10(display_factor);
|
||||
}
|
||||
|
||||
inline PriceSize pricesize_of_price_size(const onixs::PRICE price, int8_t display_factor, Size size) {
|
||||
PriceSize result(price.mantissa(), price.exponent() + display_factor, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline datetime::LLDateTime ll_of_timestamp(const onixs::Timestamp& ts) {
|
||||
return datetime::ll_from_parts(ts.year(), (int)ts.month(), ts.day(), ts.hour(), ts.minute(), ts.second(), ts.millisecond());
|
||||
}
|
||||
@@ -335,49 +332,18 @@ void MDP3Listener::onDefinition(onixs::Handler&, const onixs::Security&, const o
|
||||
_index.add_spread_legs(fsec.numeric_id, leg_exchange_ids);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Book
|
||||
void MDP3Listener::onBookUpdate(onixs::Handler&, const onixs::Security& sec, const onixs::ConsolidatedBook& book) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", 0,_latest_sequence);
|
||||
|
||||
auto data = _index.get_security(sec.id());
|
||||
if (data == nullptr) return;
|
||||
bts_assert(data->id == (uint32_t)sec.id());
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
|
||||
// Put the data into the cache. Flush at the end of the packet.
|
||||
auto key = data->handle.key();
|
||||
auto find_iter = std::find_if(_book_updates.begin(), _book_updates.end(), [key](const BookData& entry) {
|
||||
return key == entry.handle.key();
|
||||
});
|
||||
bool found = find_iter != _book_updates.end();
|
||||
if (!found) {
|
||||
_book_updates.emplace_back(data->handle);
|
||||
}
|
||||
BookData& cache = found ? *find_iter : _book_updates.back();
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = book.bids().size();
|
||||
size_t asks_size = book.offers().size();
|
||||
if (!data->handle.has_depth()) {
|
||||
bids_size = min(bids_size, size_t { 1 });
|
||||
asks_size = min(asks_size, size_t { 1 });
|
||||
}
|
||||
for (size_t x = 0; x < bids_size; ++x) {
|
||||
cache.bids[x] = pricesize_of_price_size(book.bids()[x].price(), display_factor, book.bids()[x].quantity());
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x) {
|
||||
cache.asks[x] = pricesize_of_price_size(book.offers()[x].price(), display_factor, book.offers()[x].quantity());
|
||||
}
|
||||
// NaN out the remaining depth
|
||||
for (size_t x = bids_size; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
cache.bids[x] = PriceSize::NO_MARKET;
|
||||
}
|
||||
for (size_t x = asks_size; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
cache.asks[x] = PriceSize::NO_MARKET;
|
||||
void MDP3Listener::onBookUpdate(onixs::Handler&, const onixs::Security& sec, const onixs::DirectBook& book) {
|
||||
if (_booktype == CMEOptions::BOOKTYPE_DIRECT) {
|
||||
onBookUpdate(sec, book);
|
||||
}
|
||||
}
|
||||
|
||||
void MDP3Listener::onBookUpdate(onixs::Handler&, const onixs::Security& sec, const onixs::ConsolidatedBook& book) {
|
||||
if (_booktype == CMEOptions::BOOKTYPE_CONSOLIDATED) {
|
||||
onBookUpdate(sec, book);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Trades
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "lib/utils/bts_no_copy.h"
|
||||
#include "lib/md/coconut2.h"
|
||||
#include "CMESecurityIndex.h"
|
||||
#include "server/sparkd/tracing/bts_trace.h"
|
||||
|
||||
#include "onixs.h"
|
||||
#include <tbb/concurrent_queue.h>
|
||||
@@ -13,13 +14,23 @@
|
||||
namespace bts {
|
||||
namespace md {
|
||||
|
||||
inline PriceSize pricesize_of_price_size(const onixs::PRICE price, int8_t display_factor, Size size) {
|
||||
PriceSize result(price.mantissa(), price.exponent() + display_factor, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
class MDP3Listener :
|
||||
public onixs::HandlerListener,
|
||||
public onixs::MarketDataListener,
|
||||
public onixs::SecurityListener
|
||||
{
|
||||
public:
|
||||
MDP3Listener(CMESecurityIndex& index, MDProducer&, SecurityIDRecorder&, co2::SettlementRecorder&, std::vector<MDProducerChannel>);
|
||||
MDP3Listener(CMESecurityIndex& index,
|
||||
MDProducer&,
|
||||
SecurityIDRecorder&,
|
||||
co2::SettlementRecorder&,
|
||||
const std::vector<MDProducerChannel>,
|
||||
CMEOptions::BookType);
|
||||
virtual ~MDP3Listener();
|
||||
|
||||
//
|
||||
@@ -51,10 +62,52 @@ public:
|
||||
//
|
||||
// Book
|
||||
// void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::MboBook&) override;
|
||||
// void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::DirectBook&) override;
|
||||
void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::DirectBook&) override;
|
||||
// void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::ImpliedBook&) override;
|
||||
void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::ConsolidatedBook&) override;
|
||||
|
||||
template <class TBook>
|
||||
void onBookUpdate(const onixs::Security& sec, const TBook& book) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", 0,_latest_sequence);
|
||||
|
||||
auto data = _index.get_security(sec.id());
|
||||
if (data == nullptr) return;
|
||||
bts_assert(data->id == (uint32_t)sec.id());
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
|
||||
// Put the data into the cache. Flush at the end of the packet.
|
||||
auto key = data->handle.key();
|
||||
auto find_iter = std::find_if(_book_updates.begin(), _book_updates.end(), [key](const BookData& entry) {
|
||||
return key == entry.handle.key();
|
||||
});
|
||||
bool found = find_iter != _book_updates.end();
|
||||
if (!found) {
|
||||
_book_updates.emplace_back(data->handle);
|
||||
}
|
||||
BookData& cache = found ? *find_iter : _book_updates.back();
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = book.bids().size();
|
||||
size_t asks_size = book.offers().size();
|
||||
if (!data->handle.has_depth()) {
|
||||
bids_size = std::min(bids_size, size_t { 1 });
|
||||
asks_size = std::min(asks_size, size_t { 1 });
|
||||
}
|
||||
for (size_t x = 0; x < bids_size; ++x) {
|
||||
cache.bids[x] = pricesize_of_price_size(book.bids()[x].price(), display_factor, book.bids()[x].quantity());
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x) {
|
||||
cache.asks[x] = pricesize_of_price_size(book.offers()[x].price(), display_factor, book.offers()[x].quantity());
|
||||
}
|
||||
// NaN out the remaining depth
|
||||
for (size_t x = bids_size; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
cache.bids[x] = PriceSize::NO_MARKET;
|
||||
}
|
||||
for (size_t x = asks_size; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
cache.asks[x] = PriceSize::NO_MARKET;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Trades
|
||||
void onTrade(onixs::Handler&, const onixs::Security&, const onixs::RecoveryEntryArgs&) override;
|
||||
@@ -125,6 +178,7 @@ private:
|
||||
std::vector<std::string> _symbol_filter_storage;
|
||||
std::set<onixs::StrRef> _symbol_filter;
|
||||
std::vector<MDProducerChannel> _channels;
|
||||
CMEOptions::BookType _booktype;
|
||||
std::atomic<int> _concurrency_monitor;
|
||||
|
||||
struct BookData {
|
||||
|
||||
@@ -213,7 +213,12 @@ unique_ptr<onixs::Handler> MDP3Main::prepare_handler(const string& channel_id) {
|
||||
settings.feeds().connectivityConfigurationFile(config_path());
|
||||
|
||||
// Market data options
|
||||
settings.bookManagement().consolidatedBooks().maintain(true);
|
||||
if (options.book_type() == CMEOptions::BOOKTYPE_CONSOLIDATED) {
|
||||
settings.bookManagement().consolidatedBooks().maintain(true);
|
||||
}
|
||||
else {
|
||||
settings.bookManagement().directBooks().maintain(true);
|
||||
}
|
||||
|
||||
// 10k default is *far* too low
|
||||
settings.recoveryQueueMaxSize(options.recovery_queue_size());
|
||||
@@ -334,12 +339,13 @@ unique_ptr<MDP3Listener> MDP3Main::prepare_listener(onixs::Handler& handler, con
|
||||
producer_channels.push_back(_producer.get_channel(symbol.symbol().c_str(), symbol.type(), symbol.exchange()));
|
||||
}
|
||||
|
||||
unique_ptr<MDP3Listener> listener(new MDP3Listener(_index, _producer, _security_recorder, _settlement_recorder, producer_channels));
|
||||
const auto& options = channel_settings_for(channel.id);
|
||||
|
||||
unique_ptr<MDP3Listener> listener(new MDP3Listener(_index, _producer, _security_recorder, _settlement_recorder, producer_channels, options.book_type()));
|
||||
for (auto& symbol : channel.symbols) {
|
||||
listener->add_filter_symbol(symbol.symbol());
|
||||
}
|
||||
|
||||
const auto& options = channel_settings_for(channel.id);
|
||||
if (options.load_security_db()) {
|
||||
// Load the list of known securities and add them to the index for fast startup
|
||||
db::load_securities(channel.symbols, [&](const api::Security& security) {
|
||||
|
||||
@@ -42,6 +42,7 @@ struct CounterMetric : public Metric {
|
||||
void decrement(T val) { _val -= val; }
|
||||
void set(T val) { _val = val; }
|
||||
};
|
||||
typedef CounterMetric<int> IntCounterMetric;
|
||||
|
||||
template <class T>
|
||||
class AtomicCounterMetric : public Metric {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include "OrderStore.h"
|
||||
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include "server/sparkd/persistence/SqliteStore.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "server/sparkd/orderman/events/StoredOrderEvent.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include "server/sparkd/orderman/events/OrderStateUpdateEvent.h"
|
||||
#include "server/sparkd/orderman/events/StoredOrderEvent.h"
|
||||
#include "server/sparkd/persistence/SqliteStore.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -13,74 +13,74 @@ namespace orderman {
|
||||
|
||||
static auto const orders_ns = "orders";
|
||||
|
||||
struct OrderStore::PImpl
|
||||
{
|
||||
PImpl() :
|
||||
known_orders(),
|
||||
store(new persistence::SqliteStore("orders")),
|
||||
order_stream()
|
||||
{}
|
||||
struct OrderStore::PImpl {
|
||||
PImpl()
|
||||
: known_orders(), store(new persistence::SqliteStore("orders")),
|
||||
order_stream() {}
|
||||
|
||||
const GatewayName gw_name;
|
||||
std::unordered_map<OrderKey,std::unique_ptr<api::orderman::OrderRecord>> known_orders;
|
||||
std::unordered_map<OrderKey, google::protobuf::Timestamp> known_orders;
|
||||
std::unique_ptr<persistence::IStore> store;
|
||||
mercury::Stream<StoredOrderEvent> order_stream;
|
||||
};
|
||||
|
||||
OrderStore::OrderStore () : me (new PImpl()) {}
|
||||
OrderStore::~OrderStore () {}
|
||||
OrderStore::OrderStore() : me(new PImpl()) {}
|
||||
OrderStore::~OrderStore() {}
|
||||
|
||||
void OrderStore::init()
|
||||
{
|
||||
void OrderStore::init() {
|
||||
api::orderman::OrderRecord rec;
|
||||
std::vector<StoredOrderEvent> stored_batch;
|
||||
|
||||
auto all = me->store->retrieve_all()[orders_ns];
|
||||
for (auto const &kvp : all)
|
||||
if (utils::pb::from_string<api::orderman::OrderRecord> (kvp.second,&rec)) {
|
||||
for (auto const &kvp : all)
|
||||
if (utils::pb::from_string<api::orderman::OrderRecord>(kvp.second,
|
||||
&rec)) {
|
||||
auto ok = OrderKey(std::stoi(kvp.first));
|
||||
auto order_record = std::make_unique<api::orderman::OrderRecord> (rec);
|
||||
stored_batch.emplace_back(*order_record.get());
|
||||
me->known_orders[ok] = std::move(order_record);
|
||||
}
|
||||
else
|
||||
LOG(OrderMan,Error,
|
||||
stored_batch.emplace_back(rec);
|
||||
me->known_orders[ok] = rec.state().created();
|
||||
} else
|
||||
LOG(OrderMan, Error,
|
||||
"Could not recover order {} from storage. "
|
||||
"This should be investigated", kvp.first);
|
||||
"This should be investigated",
|
||||
kvp.first);
|
||||
|
||||
me->order_stream = make_stream<StoredOrderEvent> ();
|
||||
LOG(OrderMan,Info,
|
||||
"Order Store published {} orders recoved from disk", all.size());
|
||||
publish_batch(me->order_stream,stored_batch);
|
||||
subscribe<OrderStateUpdateEvent> ([this] (auto const&ev) {this->on_order_state_update(ev);});
|
||||
me->order_stream = make_stream<StoredOrderEvent>();
|
||||
LOG(OrderMan, Info, "Order Store published {} orders recoved from disk",
|
||||
all.size());
|
||||
publish_batch(me->order_stream, stored_batch);
|
||||
subscribe<OrderStateUpdateEvent>(
|
||||
[this](auto const &ev) { this->on_order_state_update(ev); });
|
||||
}
|
||||
|
||||
void OrderStore::on_order_state_update(mercury::Update<OrderStateUpdateEvent> const&u)
|
||||
{
|
||||
void OrderStore::on_order_state_update(
|
||||
mercury::Update<OrderStateUpdateEvent> const &u) {
|
||||
if (u.events)
|
||||
for (auto const &ev : *u.events) {
|
||||
auto &ptr = me->known_orders[ev.key];
|
||||
auto new_order = !ptr;
|
||||
if (new_order) {
|
||||
ptr.reset (new api::orderman::OrderRecord());
|
||||
*ptr->mutable_def() = *ev.definition;
|
||||
ptr->set_key(ev.key.value());
|
||||
ptr->set_parent_key(ev.parent_key.value());
|
||||
*ptr->mutable_state()->mutable_created() = utils::pb::pb_now();
|
||||
}
|
||||
|
||||
auto created = ptr->state().created();
|
||||
*ptr->mutable_state() = ev.state;
|
||||
*ptr->mutable_state()->mutable_created() = created;
|
||||
*ptr->mutable_state()->mutable_last_change() = utils::pb::pb_now();
|
||||
auto k = std::to_string(ptr->key());
|
||||
auto v = utils::pb::to_string(*ptr);
|
||||
if (new_order)
|
||||
me->store->insert(orders_ns,k,v);
|
||||
else
|
||||
me->store->update(orders_ns,k,v);
|
||||
publish(me->order_stream,*ptr.get());
|
||||
api::orderman::OrderRecord order;
|
||||
order.set_key(ev.key.value());
|
||||
order.set_parent_key(ev.parent_key.value());
|
||||
*order.mutable_def() = *ev.definition;
|
||||
*order.mutable_state() = ev.state;
|
||||
*order.mutable_state()->mutable_last_change() = utils::pb::pb_now();
|
||||
|
||||
auto it = me->known_orders.find(ev.key);
|
||||
if (it == me->known_orders.end()) {
|
||||
// new order
|
||||
auto now = utils::pb::pb_now();
|
||||
me->known_orders[ev.key] = now;
|
||||
*order.mutable_state()->mutable_created() = now;
|
||||
me->store->insert(orders_ns, std::to_string(order.key()),
|
||||
utils::pb::to_string(order));
|
||||
} else {
|
||||
*order.mutable_state()->mutable_created() = it->second;
|
||||
me->store->update(orders_ns, std::to_string(order.key()),
|
||||
utils::pb::to_string(order));
|
||||
}
|
||||
LOG(OrderMan, Diag, "Storing order {}", utils::pb::to_string(order));
|
||||
publish(me->order_stream, order);
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -92,10 +92,10 @@ void ServiceBridge::on(mercury::Update<StoredOrderEvent> const &update) {
|
||||
bool was_ready = is_ready();
|
||||
me->have_orders = true;
|
||||
|
||||
std::vector<api::orderman::OrderRecord const *> orders;
|
||||
std::vector<api::orderman::OrderRecord> orders;
|
||||
orders.reserve(update.events->size());
|
||||
for (auto ev : *(update.events)) {
|
||||
me->known_orders.insert(OrderKey(ev.order_record->key()));
|
||||
me->known_orders.insert(OrderKey(ev.order_record.key()));
|
||||
orders.push_back(ev.order_record);
|
||||
}
|
||||
me->on_orders(orders);
|
||||
|
||||
@@ -39,8 +39,8 @@ public:
|
||||
template <class EV> using OnEv = std::function<void(EV const&)>;
|
||||
|
||||
using OnReady = std::function<void()>;
|
||||
using OnOrders = std::function<void (std::vector<api::orderman::OrderRecord const*> )>;
|
||||
using OnFills = std::function<void (std::vector<api::orderman::Fill const*> )>;
|
||||
using OnOrders = std::function<void (std::vector<api::orderman::OrderRecord> const& )>;
|
||||
using OnFills = std::function<void (std::vector<api::orderman::Fill const*> const&)>;
|
||||
|
||||
ServiceBridge(OnReady const&, OnOrders const&, OnFills const&);
|
||||
void init() override;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "server/sparkd/orderman/actors/order_manager/OrderServicesData.h"
|
||||
#include "server/sparkd/orderman/events/NewAdminDataEvent.h"
|
||||
#include "server/sparkd/orderman/events/OrderManagerReadyEvent.h"
|
||||
#include "server/sparkd/orderman/events/StoredOrderEvent.h"
|
||||
#include "server/sparkd/orderman/order_factory/OrderFactory.h"
|
||||
#include "server/sparkd/orderman/orders/Order.h"
|
||||
#include "server/sparkd/securities/ISecurityDB.h"
|
||||
@@ -96,8 +97,8 @@ void OrderManager::on_order_recovery(
|
||||
// recover orders from disk
|
||||
for (auto const &ev : *(update.events)) {
|
||||
auto &order =
|
||||
me->services_data.order_factory.recover(*ev.order_record);
|
||||
auto const &ostate = ev.order_record->state();
|
||||
me->services_data.order_factory.recover(ev.order_record);
|
||||
auto const &ostate = ev.order_record.state();
|
||||
OrderInfo oi{order, me->services_data};
|
||||
auto serv_ptr = oi.order_services.get();
|
||||
me->orders[order.key()] = std::move(oi);
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "server/sparkd/orderman/events/NewSecuritiesEvent.h"
|
||||
#include "server/sparkd/orderman/events/OrderStateUpdateEvent.h"
|
||||
#include "server/sparkd/orderman/events/OutFIXMsgEvent.h"
|
||||
#include "server/sparkd/orderman/events/StoredOrderEvent.h"
|
||||
#include "server/sparkd/orderman/events/UpdateOrderEvent.h"
|
||||
#include "server/sparkd/orderman/limiter/Limiter.h"
|
||||
#include "server/sparkd/orderman/order_router/OrderRouter.h"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "server/sparkd/mercury/synopsis/MergeByKey.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "server/sparkd/mercury/events/Event.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
@@ -14,10 +15,10 @@ class StoredOrderEvent : public mercury::Event <StoredOrderEvent>
|
||||
public:
|
||||
using SynopsisPolicy = mercury::MergeByKey<StoredOrderEvent>;
|
||||
|
||||
StoredOrderEvent(api::orderman::OrderRecord &record) : order_record(&record) {}
|
||||
StoredOrderEvent(api::orderman::OrderRecord const &record) : order_record(record) {}
|
||||
|
||||
auto key() const {return order_record->key();}
|
||||
api::orderman::OrderRecord *order_record;
|
||||
auto key() const {return order_record.key();}
|
||||
api::orderman::OrderRecord order_record;
|
||||
};
|
||||
|
||||
}}
|
||||
@@ -88,7 +88,8 @@ void IcebergOrder::on_init_recovered(api::orderman::OrderRecord const &rec,
|
||||
}
|
||||
}
|
||||
|
||||
void IcebergOrder::on_activate(Price p, Quantity q, OrderServices &services, TickToTradeCorID) {
|
||||
void IcebergOrder::on_activate(Price p, Quantity q, OrderServices &services,
|
||||
TickToTradeCorID) {
|
||||
bts_assert(!me->services);
|
||||
me->services = &services;
|
||||
me->child_builder.activate(services);
|
||||
@@ -184,6 +185,7 @@ void IcebergOrder::transition(api::orderman::OrderStatus new_status,
|
||||
auto const &to = OrderStatus_Name(new_status);
|
||||
LOG(OrderMan, Info, "Transitioning iceberg order {} from {} to {}. Msg: {}",
|
||||
key().value(), from, to, msg);
|
||||
me->state.set_last_msg(msg);
|
||||
me->state.set_status(new_status);
|
||||
}
|
||||
|
||||
@@ -229,8 +231,9 @@ void IcebergOrder::send_next_child_order() {
|
||||
auto remaining = me->state.quantity() - me->state.filled_quantity();
|
||||
auto to_send = std::min(remaining, me->show_quantity.value());
|
||||
me->child.order = me->child_builder.build();
|
||||
LOG(OrderMan, Info, "Iceberg order {} creating new child order {} with size "
|
||||
"{} price {}. Filled {} so far out of {}",
|
||||
LOG(OrderMan, Info,
|
||||
"Iceberg order {} creating new child order {} with size "
|
||||
"{} price {}. Filled {} so far out of {}",
|
||||
key().value(), me->child.order.key().value(), to_send, me->state.price(),
|
||||
me->state.filled_quantity(), me->state.quantity());
|
||||
|
||||
@@ -251,14 +254,20 @@ void IcebergOrder::on_child_order_update(OrderStateUpdateEvent const &ev) {
|
||||
break;
|
||||
|
||||
case ORDER_STATUS_COMPLETED: {
|
||||
api::orderman::OrderState merged_state;
|
||||
merge_states(me->state, me->child.state, &merged_state);
|
||||
me->state = merged_state;
|
||||
me->child.clear();
|
||||
if (me->state.quantity() > me->state.filled_quantity())
|
||||
send_next_child_order();
|
||||
else
|
||||
transition(ORDER_STATUS_COMPLETED);
|
||||
if (me->child.state.filled_quantity() > 0) {
|
||||
api::orderman::OrderState merged_state;
|
||||
merge_states(me->state, me->child.state, &merged_state);
|
||||
me->state = merged_state;
|
||||
me->child.clear();
|
||||
if (me->state.quantity() > me->state.filled_quantity())
|
||||
send_next_child_order();
|
||||
else
|
||||
transition(ORDER_STATUS_COMPLETED);
|
||||
} else {
|
||||
me->child.clear();
|
||||
transition(ORDER_STATUS_ERROR,
|
||||
"Child order completed without fills");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -302,5 +311,5 @@ void IcebergOrder::on_child_order_update(OrderStateUpdateEvent const &ev) {
|
||||
}
|
||||
report_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -126,9 +126,10 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
|
||||
|
||||
// bail if we can't find the security for the order
|
||||
if (!me->sec) {
|
||||
LOG(OrderMan, Error, "Unknown security {} referenced by recovered "
|
||||
"order {}. This is a serious "
|
||||
"problem which has to be corrected manually. ",
|
||||
LOG(OrderMan, Error,
|
||||
"Unknown security {} referenced by recovered "
|
||||
"order {}. This is a serious "
|
||||
"problem which has to be corrected manually. ",
|
||||
def.security_key(), rec.key());
|
||||
transition_to_error(
|
||||
NativeOrderState::SUB_ERROR_UNKNOWN_SECURITY,
|
||||
@@ -290,8 +291,9 @@ void NativeOrder::on_activate(Price p, Quantity q,
|
||||
|
||||
// is our gateway connected?
|
||||
if (!me->session_services->is_gateway_connected()) {
|
||||
LOG(OrderMan, Warn, "Attempt to send order to a disconnected gateway. "
|
||||
"Dropping order {}",
|
||||
LOG(OrderMan, Warn,
|
||||
"Attempt to send order to a disconnected gateway. "
|
||||
"Dropping order {}",
|
||||
key().value());
|
||||
transition(ORDER_STATUS_REJECTED_BY_SPARK, "Exchange gateway is down");
|
||||
break;
|
||||
@@ -302,7 +304,8 @@ void NativeOrder::on_activate(Price p, Quantity q,
|
||||
UserID(me->definition.user_id()),
|
||||
AccountID(me->definition.account_id()));
|
||||
if (!lqr.ok) {
|
||||
LOG(OrderMan, Warn, "Unauthorized order attempted. User: {} on "
|
||||
LOG(OrderMan, Warn,
|
||||
"Unauthorized order attempted. User: {} on "
|
||||
"Account: {}. Msg: {} Order: {}. Dropping order",
|
||||
me->definition.user(), me->definition.account(), lqr.error_msg,
|
||||
key().value());
|
||||
@@ -318,8 +321,9 @@ void NativeOrder::on_activate(Price p, Quantity q,
|
||||
me->definition.side(), q);
|
||||
|
||||
if (!lqr.ok) {
|
||||
LOG(OrderMan, Warn, "New order would breach limit. User: {} Account: "
|
||||
"{} Msg: {} Order: {}. Dropping order",
|
||||
LOG(OrderMan, Warn,
|
||||
"New order would breach limit. User: {} Account: "
|
||||
"{} Msg: {} Order: {}. Dropping order",
|
||||
me->definition.user(), me->definition.account(), lqr.error_msg,
|
||||
key().value());
|
||||
|
||||
@@ -330,8 +334,9 @@ void NativeOrder::on_activate(Price p, Quantity q,
|
||||
|
||||
// are we under the messaging threshold?
|
||||
if (!me->session_services->governor().can_send()) {
|
||||
LOG(OrderMan, Warn, "New order would cause breach of messaging "
|
||||
"limits. Dropping order {}",
|
||||
LOG(OrderMan, Warn,
|
||||
"New order would cause breach of messaging "
|
||||
"limits. Dropping order {}",
|
||||
key().value());
|
||||
transition(ORDER_STATUS_REJECTED_BY_SPARK,
|
||||
"Messaging threshold breached");
|
||||
@@ -747,8 +752,9 @@ void NativeOrder::report_state() {
|
||||
me->definition.side(),
|
||||
Quantity(previous_position.value() - current_position.value()));
|
||||
} else
|
||||
LOG(OrderMan, Error, "Order {} ignored for purposes of limit enforcing "
|
||||
"because it references unknown security {} ",
|
||||
LOG(OrderMan, Error,
|
||||
"Order {} ignored for purposes of limit enforcing "
|
||||
"because it references unknown security {} ",
|
||||
key().value(), me->definition.security_key());
|
||||
|
||||
me->services->report_state(me->state);
|
||||
@@ -845,8 +851,10 @@ void NativeOrder::init_fix() {
|
||||
auto stop_px = def.custom_def().stop().stop_price();
|
||||
auto encoded_stop_px =
|
||||
securities::encode_exchange_price(*me->sec, Price(stop_px)).value();
|
||||
create_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
|
||||
update_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
|
||||
create_encoding.add<fix::StopPx>(encoded_stop_px,
|
||||
me->price_encoding_precision, -1);
|
||||
update_encoding.add<fix::StopPx>(encoded_stop_px,
|
||||
me->price_encoding_precision, -1);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -860,8 +868,10 @@ void NativeOrder::init_fix() {
|
||||
auto stop_px = def.custom_def().stop_limit().stop_price();
|
||||
auto encoded_stop_px =
|
||||
securities::encode_exchange_price(*me->sec, Price(stop_px)).value();
|
||||
create_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
|
||||
update_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
|
||||
create_encoding.add<fix::StopPx>(encoded_stop_px,
|
||||
me->price_encoding_precision, -1);
|
||||
update_encoding.add<fix::StopPx>(encoded_stop_px,
|
||||
me->price_encoding_precision, -1);
|
||||
|
||||
// price
|
||||
me->create_price_bm = create_encoding.add<fix::Price>(
|
||||
@@ -1021,5 +1031,5 @@ NativeOrder::get_native_state(api::orderman::OrderState &st) {
|
||||
bts_assert(false);
|
||||
return *st.mutable_stop_limit()->mutable_native_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -53,7 +53,7 @@ void SpreaderOrder::on_activate(Price price, Quantity quantity,
|
||||
}
|
||||
void SpreaderOrder::on_update(Price price, Quantity quantity,TickToTradeCorID){
|
||||
if (!_active) return;
|
||||
LOG(OrderMan, Info, "SpreaderOrder.on_update key: {} price: {} quantity: {}", key().value(), price.value(), quantity.value());
|
||||
LOG(OrderMan, Info, "SpreaderOrder.on_update key: {} price: {} quantity: {}", key().value(), price.value(), quantity.value());
|
||||
_state_worker->on_update(price, quantity);
|
||||
_report_state();
|
||||
}
|
||||
@@ -242,45 +242,50 @@ bool SpreaderOrder::_quoters_can_accept_modification (){
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void SpreaderOrder::_send_hedge(size_t filled_leg_index,
|
||||
Price filled_price,
|
||||
Quantity filled_quantity,
|
||||
size_t hedge_leg_index){
|
||||
LOG(OrderMan, Info, "SpreaderOrder::_send_hedge key: {}, filled_leg_index: {} filled_price: {} filled_quantity: {} hedge_leg_index: {}",
|
||||
key().value(), filled_leg_index, filled_price.value(), filled_quantity.value(), hedge_leg_index);
|
||||
Price hedge_price;
|
||||
Quantity hedge_quantity;
|
||||
_compute_hedge_price_quantity (filled_leg_index, filled_price,
|
||||
filled_quantity, hedge_leg_index,
|
||||
hedge_price, hedge_quantity);
|
||||
SingleFillHedger& hedger = _bookkeeping_data().add_new_hedger (*this,
|
||||
void SpreaderOrder::_send_hedge(size_t hedge_leg_index,
|
||||
Price hedge_price,
|
||||
Quantity hedge_qty){
|
||||
LOG(OrderMan, Info, "SpreaderOrder::_send_hedge key: {}, hedge_leg_index: {} hedge_price: {} hedge_qty: {}",
|
||||
key().value(), hedge_leg_index, hedge_price, hedge_qty);
|
||||
SingleFillHedger& hedger = _bookkeeping_data().add_new_hedger (*this,
|
||||
hedge_leg_index,
|
||||
hedge_price,
|
||||
hedge_quantity);
|
||||
hedge_qty);
|
||||
hedger.init ();
|
||||
}
|
||||
void SpreaderOrder::_compute_hedge_price_quantity(size_t filled_leg_index,
|
||||
Price fill_price, Quantity fill_quantity,
|
||||
Price fill_price,
|
||||
double fill_quantity,
|
||||
size_t hedge_leg_index,
|
||||
Price& hedge_price, Quantity& hedge_quantity){
|
||||
Price& hedge_price,
|
||||
double& hedge_quantity){
|
||||
Price computed_hedge_price;
|
||||
SpreaderLegData& filled_leg_data = _bookkeeping_data().get_leg_data(filled_leg_index);
|
||||
SpreaderLegData& hedged_leg_data = _bookkeeping_data().get_leg_data(hedge_leg_index);
|
||||
if (_bookkeeping_data().n_legs() == 2)
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (_bookkeeping_data().price(),
|
||||
filled_leg_data.leg_property_data(), fill_price, fill_quantity,
|
||||
hedged_leg_data.leg_property_data(), computed_hedge_price, hedge_quantity);
|
||||
filled_leg_data.leg_property_data(),
|
||||
fill_price,
|
||||
fill_quantity,
|
||||
hedged_leg_data.leg_property_data(),
|
||||
computed_hedge_price,
|
||||
hedge_quantity);
|
||||
else{
|
||||
const QuoterLegDriverData& driver_data = filled_leg_data.quoter().driver_data(hedge_leg_index);
|
||||
SpreaderUtils::compute_hedge_price_size_n_legs (filled_leg_data.leg_property_data(), fill_price, fill_quantity,
|
||||
hedged_leg_data.leg_property_data(), driver_data,
|
||||
computed_hedge_price, hedge_quantity);
|
||||
const QuoteLegDriver& driver_data = filled_leg_data.quoter().driver_data(fill_price, hedge_leg_index);
|
||||
SpreaderUtils::compute_hedge_price_size_n_legs (filled_leg_data.leg_property_data(),
|
||||
fill_price,
|
||||
fill_quantity,
|
||||
hedged_leg_data.leg_property_data(),
|
||||
driver_data,
|
||||
computed_hedge_price,
|
||||
hedge_quantity);
|
||||
}
|
||||
hedge_price = SpreaderUtils::apply_pre_hedge_parameters (hedged_leg_data.leg_property_data().leg_params().pre_hedge_params(),
|
||||
hedged_leg_data.book(),
|
||||
hedged_leg_data.leg_property_data().trading_side(),
|
||||
hedged_leg_data.leg_property_data().tick_size(),
|
||||
computed_hedge_price, hedge_quantity);
|
||||
Price(computed_hedge_price),
|
||||
Quantity(hedge_quantity));
|
||||
}
|
||||
void SpreaderOrder::_report_state (){
|
||||
if (_activated()){
|
||||
|
||||
@@ -166,17 +166,15 @@ private:
|
||||
// on leg 'leg_to_hedge_index' in response to a fill
|
||||
// on leg 'filled_leg_index' with price 'filled_price'
|
||||
// quantity 'filled_quantity'
|
||||
void _send_hedge(size_t filled_leg_index, Price filled_price,
|
||||
Quantity filled_quantity,
|
||||
size_t leg_to_hedge_index);
|
||||
// purely a computating function that computes the
|
||||
// hedge price and quantity (last 2 args) for leg
|
||||
// 'hedged_leg_index' for a fill on leg 'filled_leg_index'
|
||||
// with price 'filled_price' and quantity 'filled_quantity'
|
||||
void _send_hedge(size_t hedge_leg_index,
|
||||
Price hedge_price,
|
||||
Quantity hedge_qty);
|
||||
void _compute_hedge_price_quantity(size_t filled_leg_index,
|
||||
Price filled_price, Quantity filled_quantity,
|
||||
Price filled_price,
|
||||
double filled_quantity,
|
||||
size_t hedged_leg_index,
|
||||
Price& hedge_price, Quantity& hedge_quantity);
|
||||
Price& hedge_price,
|
||||
double& hedge_quantity);
|
||||
// sends the report out
|
||||
void _report_state ();
|
||||
SpreaderBookkeepingData& _bookkeeping_data(){
|
||||
|
||||
@@ -20,6 +20,7 @@ _spreader(spreader),
|
||||
_this_leg_data (this_leg_property),
|
||||
_n_spreader_legs (n_spreader_legs),
|
||||
_child_order_state (child_order_state),
|
||||
_driver_cache(n_spreader_legs,n_spreader_legs>2?10:0,this_leg_property.tick_size()),
|
||||
_price_quantity_state(Price(0), Quantity(0), child_order_state),
|
||||
_is_permenantly_cancelled (false),
|
||||
_worker(nullptr),
|
||||
@@ -33,8 +34,7 @@ _end_state_worker(this){
|
||||
if (_active_quoting){
|
||||
if (!_two_legged){
|
||||
for (size_t q_idx = 0; q_idx < _n_spreader_legs; q_idx++){
|
||||
_drivers_commited.push_back (QuoterLegDriverData());
|
||||
_drivers_temp_usage.push_back (QuoterLegDriverData());
|
||||
_drivers_temp_usage.push_back (QuoteLegDriver());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ _this_leg_data (other._this_leg_data),
|
||||
_n_spreader_legs (other._n_spreader_legs),
|
||||
_child_order_state (other._child_order_state),
|
||||
_drivers_temp_usage(other._drivers_temp_usage),
|
||||
_drivers_commited (other._drivers_commited),
|
||||
_driver_cache(other._driver_cache),
|
||||
_price_quantity_state(other._price_quantity_state.target_spread_price(),
|
||||
other._price_quantity_state.target_leg_quantity(),
|
||||
other._price_quantity_state.order_state()),
|
||||
@@ -131,13 +131,8 @@ bool SingleLegQuoter::is_terminal_state () const {
|
||||
bool SingleLegQuoter::accept_modifications() const {
|
||||
return _worker->state() == QuoterState::WAITING_FOR_SIZE || _worker->state() == QuoterState::WORKING;
|
||||
}
|
||||
Price SingleLegQuoter::driver_price (size_t leg_idx) const{
|
||||
bts_assert (leg_idx < _n_spreader_legs);
|
||||
return _drivers_commited[leg_idx].driver_price;
|
||||
}
|
||||
void SingleLegQuoter::_incorporate_other_leg_fill (size_t leg_index, Quantity filled_quantity){
|
||||
Quantity this_leg_reduction = SpreaderUtils::get_leg_quantity_from_another_leg_quantity (_spreader.get_leg_data(leg_index), filled_quantity, _this_leg_data);
|
||||
_price_quantity_state.add_hedged_reduction (this_leg_reduction);
|
||||
void SingleLegQuoter::_incorporate_other_leg_fill (size_t, Quantity hedge_reduction){
|
||||
_price_quantity_state.add_hedged_reduction (hedge_reduction);
|
||||
}
|
||||
void SingleLegQuoter::_compute_price_quantity(Price& price, Quantity& quantity){
|
||||
Quantity remaining_quantity = _price_quantity_state.remaining_to_fill();
|
||||
@@ -152,11 +147,9 @@ void SingleLegQuoter::_compute_price_quantity(Price& price, Quantity& quantity){
|
||||
SpreaderUtils::compute_quoting_price_size_n_legs (_price_quantity_state.target_spread_price(), remaining_quantity,
|
||||
_this_leg_data.leg_index(), _spreader, _drivers_temp_usage, price, quantity);
|
||||
}
|
||||
void SingleLegQuoter::_commit_drivers(){
|
||||
if (!_two_legged)
|
||||
// this could be done with a pointer swap too
|
||||
for (size_t idx = 0; idx < _n_spreader_legs; idx++)
|
||||
_drivers_commited[idx] = _drivers_temp_usage[idx];
|
||||
void SingleLegQuoter::_commit_drivers(Price quote_price){
|
||||
if (!_two_legged)
|
||||
_driver_cache.add(_drivers_temp_usage,quote_price);
|
||||
}
|
||||
/*********************************************************************************/
|
||||
/*******************************state transformations*****************************/
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForCancelAckStateWorker.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWorkingStateWorker.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterEndStateWorker.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoterLegDriverData.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoteLegDriver.h"
|
||||
#include "server/sparkd/orderman/actors/order_manager/ChildOrder.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/SpreaderChildOrderState.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoteDriverSetHistory.h"
|
||||
#include <sstream>
|
||||
|
||||
|
||||
@@ -24,7 +25,6 @@ namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class ISpreaderManager;
|
||||
//class SpreaderChildOrderState;
|
||||
class SingleLegQuoter
|
||||
{
|
||||
friend QuoterStartStateWorker;
|
||||
@@ -45,8 +45,8 @@ private:
|
||||
// legs requires auxiliary storage for drivers, which
|
||||
// _drivers_temp_usage provides. once we are ready
|
||||
// to send the orders, the drivers are committed to
|
||||
// _drivers_commited
|
||||
std::vector<QuoterLegDriverData> _drivers_temp_usage, _drivers_commited;
|
||||
std::vector<QuoteLegDriver> _drivers_temp_usage;
|
||||
QuoteDriverSetHistory _driver_cache;
|
||||
QuoteLegPriceQuantityState _price_quantity_state;
|
||||
bool _is_permenantly_cancelled;
|
||||
// state workers
|
||||
@@ -108,13 +108,10 @@ public:
|
||||
// the quoter returns true if it is in a state where orders
|
||||
// can be modified
|
||||
bool accept_modifications() const;
|
||||
// for a given leg, returns the driver price that was used
|
||||
// to price the current order
|
||||
Price driver_price (size_t leg_idx) const;
|
||||
const QuoterLegDriverData& driver_data (size_t leg_idx) const
|
||||
{
|
||||
bts_assert (leg_idx < _drivers_commited.size());
|
||||
return _drivers_commited[leg_idx];
|
||||
const QuoteLegDriver& driver_data (Price quote_price, size_t leg_idx) const{
|
||||
const QuoteLegDriver* leg_driver = _driver_cache.find_driver(quote_price,leg_idx);
|
||||
bts_assert(leg_driver!=nullptr);
|
||||
return *leg_driver;
|
||||
}
|
||||
// tells if it is expecting to get a cancel (this helps catch
|
||||
// unsolicited cancels). returns true is its expecting a cancel
|
||||
@@ -152,12 +149,12 @@ private:
|
||||
}
|
||||
void _cancel_order () {_child_order_state.cancel_order();}
|
||||
// this updates the hedge reduction in _price_quantity_state
|
||||
void _incorporate_other_leg_fill (size_t leg_index, Quantity filled_quantity);
|
||||
void _incorporate_other_leg_fill (size_t leg_index, Quantity hedge_reduction);
|
||||
// this is the function that computes the quoting price and the
|
||||
// quantity for this leg.
|
||||
void _compute_price_quantity(Price& price, Quantity& quantity);
|
||||
// copies the computed driver to committed dri
|
||||
void _commit_drivers ();
|
||||
void _commit_drivers (Price quote_price);
|
||||
void _transform_to_START ();
|
||||
void _transform_to_WAITING_FOR_SIZE ();
|
||||
void _transform_to_WAITING_FOR_FIRST_ACK ();
|
||||
|
||||
+10
-3
@@ -23,6 +23,7 @@ _reported_state(){
|
||||
_quoter_child_order_states.reserve(_n_legs);
|
||||
_quoters.reserve (_n_legs);
|
||||
_hedgers.reserve (_n_legs);
|
||||
_unhedged_deltas.reserve(_n_legs);
|
||||
_leg_data.reserve (_n_legs);
|
||||
for (const auto& lpd_iter : legs){
|
||||
_leg_prop_data.push_back(lpd_iter);
|
||||
@@ -34,9 +35,15 @@ _reported_state(){
|
||||
_quoter_child_order_states.emplace_back (empty_order,leg_property_data.leg_security_key(),_is_live,true);
|
||||
_quoters.emplace_back(spreader_manager, leg_property_data, _n_legs, _quoter_child_order_states.back());
|
||||
_hedgers.emplace_back ();
|
||||
_leg_data.emplace_back(leg_property_data, _quoters.back(), _quoter_order_builders.back(),
|
||||
_hedger_order_builders.back(), _hedgers.back (), _quoter_child_order_states.back(),
|
||||
_books.back());
|
||||
_unhedged_deltas.emplace_back();
|
||||
_leg_data.emplace_back(leg_property_data,
|
||||
_quoters.back(),
|
||||
_quoter_order_builders.back(),
|
||||
_hedger_order_builders.back(),
|
||||
_hedgers.back (),
|
||||
_quoter_child_order_states.back(),
|
||||
_books.back(),
|
||||
_unhedged_deltas.back());
|
||||
bts_assert (_sec_key_to_index.find(leg_property_data.security_key()) == _sec_key_to_index.end());
|
||||
_sec_key_to_index.insert(std::pair<uint32_t, size_t>(leg_property_data.security_key(), leg_property_data.leg_index()));
|
||||
}
|
||||
|
||||
+3
-1
@@ -8,6 +8,7 @@
|
||||
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
|
||||
#include "server/sparkd/orderman/orders/PreActivationOrderServices.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderBuilder.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/hedger/UnhedgedDelta.h"
|
||||
|
||||
namespace bts{
|
||||
namespace orderman {
|
||||
@@ -32,7 +33,8 @@ private:
|
||||
std::vector<SpreaderChildOrderState> _quoter_child_order_states;
|
||||
std::vector<SingleLegQuoter> _quoters;
|
||||
std::vector<std::vector<SingleFillHedger>> _hedgers;
|
||||
std::vector<SpreaderLegData> _leg_data;
|
||||
std::vector<UnhedgedDelta> _unhedged_deltas;
|
||||
std::vector<SpreaderLegData> _leg_data;
|
||||
boost::container::flat_map<uint32_t, size_t> _sec_key_to_index;
|
||||
// state thats sent out in report (its kept here amd modified
|
||||
// vs creating a new one everytime)
|
||||
|
||||
@@ -9,7 +9,8 @@ SpreaderLegData::SpreaderLegData (const LegPropertyData& leg_property_data,
|
||||
SpreaderOrderBuilder& hedger_order_builder,
|
||||
std::vector<SingleFillHedger>& hedger_data_vector,
|
||||
SpreaderChildOrderState& quoter_child_order_state,
|
||||
Book& book)
|
||||
Book& book,
|
||||
UnhedgedDelta& unhedged_delta)
|
||||
:
|
||||
_leg_property_data (leg_property_data),
|
||||
_quoter(quoter),
|
||||
@@ -17,7 +18,8 @@ _quoter_order_builder (quoter_order_builder),
|
||||
_hedger_order_builder (hedger_order_builder),
|
||||
_quoter_child_order_state (quoter_child_order_state),
|
||||
_hedger_data_vector (hedger_data_vector),
|
||||
_book(book)
|
||||
_book(book),
|
||||
_unhedged_delta(unhedged_delta)
|
||||
{}
|
||||
|
||||
SpreaderLegData::SpreaderLegData(SpreaderLegData&& other)
|
||||
@@ -28,7 +30,8 @@ _quoter_order_builder(other._quoter_order_builder),
|
||||
_hedger_order_builder(other._hedger_order_builder),
|
||||
_quoter_child_order_state (other._quoter_child_order_state),
|
||||
_hedger_data_vector (other._hedger_data_vector),
|
||||
_book(other._book){
|
||||
_book(other._book),
|
||||
_unhedged_delta(other._unhedged_delta){
|
||||
bts_assert (false);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "SpreaderChildOrderState.h"
|
||||
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderBuilder.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/hedger/UnhedgedDelta.h"
|
||||
|
||||
namespace bts{
|
||||
namespace orderman{
|
||||
@@ -28,6 +29,7 @@ private:
|
||||
SpreaderChildOrderState& _quoter_child_order_state;
|
||||
std::vector<SingleFillHedger>& _hedger_data_vector;
|
||||
Book& _book;
|
||||
UnhedgedDelta& _unhedged_delta;
|
||||
public:
|
||||
SpreaderLegData (const LegPropertyData& leg_property_data,
|
||||
SingleLegQuoter& quoter,
|
||||
@@ -35,7 +37,8 @@ public:
|
||||
SpreaderOrderBuilder& hedger_order_builder,
|
||||
std::vector<SingleFillHedger>& hedger_data_vector,
|
||||
SpreaderChildOrderState& quoter_child_order_state,
|
||||
Book& book);
|
||||
Book& book,
|
||||
UnhedgedDelta& unhedged_delta);
|
||||
SpreaderLegData() = delete;
|
||||
SpreaderLegData(const SpreaderLegData&) = delete;
|
||||
// this cons is not deleted since for each leg,
|
||||
@@ -51,12 +54,13 @@ public:
|
||||
/////////////////////////////////
|
||||
const LegPropertyData& leg_property_data() const {return _leg_property_data;}
|
||||
SingleLegQuoter& quoter() {return _quoter;}
|
||||
const bts::orderman::Book& book () const {return _book;}
|
||||
const bts::orderman::Book& book () const {return _book;}
|
||||
SpreaderChildOrderState& order_update_message () {return _quoter_child_order_state;}
|
||||
bts::orderman::OrderKey key() {return _quoter_child_order_state.order_key();}
|
||||
bts::orderman::OrderKey order_key() {return _quoter_child_order_state.order_key();}
|
||||
SpreaderOrderBuilder& quoter_order_builder() {return _quoter_order_builder;}
|
||||
SpreaderOrderBuilder& hedger_order_builder() {return _hedger_order_builder;}
|
||||
UnhedgedDelta& unhedged_delta() {return _unhedged_delta;}
|
||||
// returns the number of active hedgers
|
||||
size_t n_hedgers() const { return _hedger_data_vector.size(); }
|
||||
// returns the hedger at a given index
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "server/sparkd/orderman/orderman_utils/Book.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/LegPropertyData.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/ISpreaderManager.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoterLegDriverData.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoteLegDriver.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
|
||||
|
||||
@@ -423,17 +423,28 @@ public:
|
||||
// so if we get an N lot fill in l2, we send a hedge on l1
|
||||
// sized s1 * N / s2
|
||||
////////////////////////general functions
|
||||
static double get_leg_quantity_from_another_leg_quantity (const LegPropertyData& source_leg,
|
||||
double source_leg_quantity,
|
||||
const LegPropertyData& target_leg){
|
||||
return std::abs(target_leg.size_multiplier () * source_leg_quantity / source_leg.size_multiplier ());
|
||||
}
|
||||
static Quantity get_leg_quantity_from_another_leg_quantity (const LegPropertyData& source_leg,
|
||||
Quantity source_leg_quantity,
|
||||
const LegPropertyData& target_leg){
|
||||
double rv = std::abs(target_leg.size_multiplier () * source_leg_quantity.value () / source_leg.size_multiplier ());
|
||||
double rv = get_leg_quantity_from_another_leg_quantity(source_leg,
|
||||
static_cast<double>(source_leg_quantity.value()),
|
||||
target_leg);
|
||||
return Quantity(_round_leg_qty_computed_from_another_leg_qty(rv));
|
||||
}
|
||||
}
|
||||
// given the quantity of the spreader order, determines the quantity
|
||||
// of the leg
|
||||
static double get_leg_quantity_from_spreader_quantity(const LegPropertyData& leg,
|
||||
double spreader_order_quantity){
|
||||
return std::abs (leg.size_multiplier () * spreader_order_quantity);
|
||||
}
|
||||
static Quantity get_leg_quantity_from_spreader_quantity(const LegPropertyData& leg,
|
||||
Quantity spreader_order_quantity){
|
||||
double rv = std::abs (leg.size_multiplier () * spreader_order_quantity.value());
|
||||
double rv = get_leg_quantity_from_spreader_quantity(leg,static_cast<double>(spreader_order_quantity.value()));
|
||||
return Quantity (_round_leg_qty_computed_from_spreader_qty (rv));
|
||||
}
|
||||
// finds the average weighted price and total quantity
|
||||
@@ -450,53 +461,71 @@ public:
|
||||
out_avg_price = Price((base_price.value() * base_qty.value() + inc_price.value() * inc_qty.value())/out_total_qty.value());
|
||||
}
|
||||
////////////////////////2 legged spreads
|
||||
// given a quoting target qty, finds the "driver" price
|
||||
// and qty, thats needed to hedge the quote. It also
|
||||
// finds the ammended quoting target (since the hedge
|
||||
// book might not have enough to support the entire
|
||||
// quoting target qty)
|
||||
static void compute_driver(Quantity quote_target,
|
||||
const LegPropertyData& quote_leg_data,
|
||||
const LegPropertyData& hedge_leg_data,
|
||||
const bts::orderman::Book& hedge_leg_book,
|
||||
Price& out_hedge_price,
|
||||
Quantity& out_hedge_quantity,
|
||||
Quantity& out_quote_target){
|
||||
// effective_mult is the number of contracts needed to
|
||||
// hedge one quoting contract
|
||||
double effective_mult = std::abs(hedge_leg_data.leg_params().quote_params().lean_quantity_ratio() * hedge_leg_data.size_multiplier() / quote_leg_data.size_multiplier());
|
||||
uint32_t hedge_requirement = _round_hedge_target (effective_mult * quote_target.value()); // rounding hedging up
|
||||
SpreaderUtils::traverse_depth_for_quantity(hedge_leg_book,
|
||||
SpreaderUtils::invert_side (hedge_leg_data.trading_side()),
|
||||
Quantity(hedge_requirement),
|
||||
out_hedge_price,
|
||||
out_hedge_quantity);
|
||||
// when we floor the quoting target (ie _round_quoting_qty)
|
||||
// we are essentially demanding less in terms of hedging, ex :
|
||||
// consider the hedge book depth with qty's 5, 10, 5, 20, 10 (5 is the inside)
|
||||
// and hedge ratio 20 and target qty = 5, so effectively target = 100,
|
||||
// we get 50 at the last level, out_qty = _round_quoting_qty (50/20) = 2,
|
||||
// which means we need 2 * 20 and should re-evaluate
|
||||
uint32_t out_quote_target_uint = _round_quoting_qty (out_hedge_quantity.value() / effective_mult); // rounding quoting down
|
||||
out_quote_target_uint = std::min(out_quote_target_uint,quote_target.value());
|
||||
out_quote_target = Quantity (out_quote_target_uint);
|
||||
}
|
||||
// calculates the computed price and quantity for a quoting
|
||||
// leg given the data for the other leg
|
||||
static void compute_price_quantity_2_legs(Price target_spread_price,
|
||||
Quantity this_leg_target_quantity,
|
||||
const LegPropertyData& this_leg_data,
|
||||
const LegPropertyData& other_leg_data,
|
||||
const bts::orderman::Book& other_leg_book,
|
||||
Price& price, Quantity& quantity){
|
||||
// other_leg_price and other_leg_quantity are the price and quantity calculated
|
||||
// from traversing the other book, for the requires size
|
||||
Price other_leg_price; Quantity other_leg_quantity;
|
||||
// this is the size ratio of the other leg vs this leg
|
||||
double effective_mult = std::abs(other_leg_data.leg_params().quote_params().lean_quantity_ratio() * other_leg_data.size_multiplier() / this_leg_data.size_multiplier());
|
||||
// to be conservative round the required size up
|
||||
uint32_t other_leg_target_quantity = _round_hedge_target (effective_mult * this_leg_target_quantity.value());
|
||||
SpreaderUtils::traverse_depth_for_quantity(other_leg_book, SpreaderUtils::invert_side (other_leg_data.trading_side()), Quantity(other_leg_target_quantity), other_leg_price, other_leg_quantity);
|
||||
// when we floor we essentially go back up the hedging ladder
|
||||
// so we should re-evaluate
|
||||
// ex, consider the hedge book depth with qty's 5, 10, 5, 20, 10 (5 is the inside)
|
||||
// and hedge ratio 20 and target qty = 5, so effectively target = 100,
|
||||
// we get 50 at the last level, out_qty = floor (50/20) = 2, so the
|
||||
// price should not be the last level but the second last level
|
||||
uint32_t out_quantity = _round_quoting_qty (other_leg_quantity.value() / effective_mult);
|
||||
quantity = Quantity (out_quantity);
|
||||
double out_price = (target_spread_price.value() - other_leg_price.value() * other_leg_data.price_multiplier())/this_leg_data.price_multiplier();
|
||||
out_price = bts::securities::raw_round_to_quote_price (out_price, this_leg_data.tick_size(), this_leg_data.trading_side());
|
||||
price = Price(out_price);
|
||||
Quantity quote_target,
|
||||
const LegPropertyData& quote_leg_data,
|
||||
const LegPropertyData& hedge_leg_data,
|
||||
const bts::orderman::Book& hedge_leg_book,
|
||||
Price& quote_price,
|
||||
Quantity& quote_qty){
|
||||
Price hedge_price;
|
||||
Quantity hedge_qty;
|
||||
compute_driver(quote_target,quote_leg_data,hedge_leg_data,hedge_leg_book,
|
||||
hedge_price,hedge_qty,quote_qty);
|
||||
double out_price = (target_spread_price.value() - hedge_price.value() * hedge_leg_data.price_multiplier())/quote_leg_data.price_multiplier();
|
||||
out_price = bts::securities::raw_round_to_quote_price (out_price, quote_leg_data.tick_size(), quote_leg_data.trading_side());
|
||||
quote_price = Price(out_price);
|
||||
}
|
||||
static void compute_hedge_price_size_2_legs (Price spread_price,
|
||||
const LegPropertyData& quote_filled_leg,
|
||||
Price quote_fill_price,
|
||||
Quantity quote_fill_quantity,
|
||||
double quote_fill_quantity,
|
||||
const LegPropertyData& hedge_leg,
|
||||
Price& out_hedge_price,
|
||||
Quantity& out_hedge_quantity){
|
||||
// for ratio spreads hedge sizes are simply rounded off (the fraction can be a setting)
|
||||
double& out_hedge_quantity){
|
||||
out_hedge_quantity = get_leg_quantity_from_another_leg_quantity (quote_filled_leg, quote_fill_quantity, hedge_leg);
|
||||
double out_hedge_price_d = (spread_price.value() - quote_fill_price.value() * quote_filled_leg.price_multiplier())/hedge_leg.price_multiplier();
|
||||
out_hedge_price_d = bts::securities::raw_round_to_quote_price (out_hedge_price_d, hedge_leg.tick_size(), hedge_leg.trading_side());
|
||||
out_hedge_price = Price(out_hedge_price_d);
|
||||
out_hedge_price = Price(bts::securities::raw_round_to_quote_price (out_hedge_price_d, hedge_leg.tick_size(), hedge_leg.trading_side()));
|
||||
}
|
||||
////////////////////////N legged case
|
||||
static void compute_quoting_price_size_n_legs (Price target_spread_price,
|
||||
Quantity quoting_leg_target_quantity,
|
||||
size_t quoting_leg_index,
|
||||
const ISpreaderManager& spreader,
|
||||
std::vector<QuoterLegDriverData>& driver_vector,
|
||||
std::vector<QuoteLegDriver>& driver_vector,
|
||||
Price& out_price,
|
||||
Quantity& out_quantity){
|
||||
compute_drivers (quoting_leg_target_quantity, quoting_leg_index, spreader, driver_vector);
|
||||
@@ -514,19 +543,23 @@ public:
|
||||
static void compute_drivers(Quantity quoting_leg_target_quantity,
|
||||
size_t quoting_leg_index,
|
||||
const ISpreaderManager& spreader,
|
||||
std::vector<QuoterLegDriverData>& out_driver_vector){
|
||||
const LegPropertyData& quoting_leg_data = spreader.get_leg_data (quoting_leg_index);
|
||||
std::vector<QuoteLegDriver>& out_driver_vector){
|
||||
const LegPropertyData& quote_leg_data = spreader.get_leg_data (quoting_leg_index);
|
||||
size_t n_legs = out_driver_vector.size();
|
||||
for (size_t idx = 0; idx < n_legs; idx++)
|
||||
if (idx != quoting_leg_index){
|
||||
const bts::orderman::Book& driver_leg_book = spreader.get_book(idx);
|
||||
const LegPropertyData& driver_leg_data = spreader.get_leg_data(idx);
|
||||
double driver_effective_mult = std::abs(driver_leg_data.leg_params().quote_params().lean_quantity_ratio() * driver_leg_data.size_multiplier() / quoting_leg_data.size_multiplier());
|
||||
uint32_t driver_leg_target_quantity = _round_hedge_target (driver_effective_mult * quoting_leg_target_quantity.value());
|
||||
Price driver_price; Quantity driver_quantity;
|
||||
SpreaderUtils::traverse_depth_for_quantity(driver_leg_book, SpreaderUtils::invert_side (driver_leg_data.trading_side()), Quantity(driver_leg_target_quantity), driver_price, driver_quantity);
|
||||
Quantity quoting_leg_calc_quantity = Quantity (_round_quoting_qty (driver_quantity.value() / driver_effective_mult));
|
||||
out_driver_vector[idx].set (driver_price, driver_quantity, quoting_leg_calc_quantity);
|
||||
const bts::orderman::Book& hedge_leg_book = spreader.get_book(idx);
|
||||
const LegPropertyData& hedge_leg_data = spreader.get_leg_data(idx);
|
||||
Price hedge_price;
|
||||
Quantity hedge_qty, quote_qty;
|
||||
compute_driver(quoting_leg_target_quantity,
|
||||
quote_leg_data,
|
||||
hedge_leg_data,
|
||||
hedge_leg_book,
|
||||
hedge_price,
|
||||
hedge_qty,
|
||||
quote_qty);
|
||||
out_driver_vector[idx].set (hedge_price, hedge_qty, quote_qty);
|
||||
}
|
||||
}
|
||||
// computes the quoting price from the leg drivers
|
||||
@@ -534,13 +567,13 @@ public:
|
||||
static Price compute_quoting_price_from_drivers(Price target_spread_price,
|
||||
size_t quoting_leg_index,
|
||||
const ISpreaderManager& spreader,
|
||||
const std::vector<QuoterLegDriverData>& driver_vector){
|
||||
const std::vector<QuoteLegDriver>& driver_vector){
|
||||
const LegPropertyData& quoting_leg_data = spreader.get_leg_data (quoting_leg_index);
|
||||
size_t n_legs = driver_vector.size();
|
||||
double sum = 0;
|
||||
for (size_t idx = 0; idx < n_legs; idx++)
|
||||
if (idx != quoting_leg_index)
|
||||
sum += driver_vector[idx].driver_price.value() * spreader.get_leg_data (idx).price_multiplier();
|
||||
sum += driver_vector[idx].driver_price().value() * spreader.get_leg_data (idx).price_multiplier();
|
||||
double rv = (target_spread_price.value() - sum)/quoting_leg_data.price_multiplier();
|
||||
rv = bts::securities::raw_round_to_quote_price (rv, quoting_leg_data.tick_size(), quoting_leg_data.trading_side());
|
||||
return Price(rv);
|
||||
@@ -549,26 +582,40 @@ public:
|
||||
// what each hedging leg can support. this function
|
||||
// simply finds that minimum
|
||||
static Quantity compute_quoting_quantity_from_drivers (size_t quoting_leg_index,
|
||||
const std::vector<QuoterLegDriverData>& driver_vector){
|
||||
const std::vector<QuoteLegDriver>& driver_vector){
|
||||
uint32_t rv = std::numeric_limits<uint32_t>::max();
|
||||
for (size_t idx = 0; idx < driver_vector.size(); idx++)
|
||||
if (idx != quoting_leg_index)
|
||||
if (driver_vector[idx].quoter_leg_quantity.value() < rv)
|
||||
rv = driver_vector[idx].quoter_leg_quantity.value();
|
||||
if (driver_vector[idx].quoter_leg_quantity().value() < rv)
|
||||
rv = driver_vector[idx].quoter_leg_quantity().value();
|
||||
return Quantity(rv);
|
||||
}
|
||||
static void compute_hedge_price_size_n_legs (const LegPropertyData& quote_filled_leg,
|
||||
Price,
|
||||
Quantity quote_fill_quantity,
|
||||
double quote_fill_quantity,
|
||||
const LegPropertyData& hedge_leg,
|
||||
const QuoterLegDriverData& leg_driver_data,
|
||||
Price& out_hedge_price, Quantity& out_hedge_quantity){
|
||||
out_hedge_price = leg_driver_data.driver_price;
|
||||
out_hedge_quantity = get_leg_quantity_from_another_leg_quantity (quote_filled_leg, quote_fill_quantity, hedge_leg);
|
||||
const QuoteLegDriver& leg_driver_data,
|
||||
Price& out_hedge_price,
|
||||
double& out_hedge_quantity){
|
||||
out_hedge_price = Price(leg_driver_data.driver_price().value());
|
||||
out_hedge_quantity = get_leg_quantity_from_another_leg_quantity (quote_filled_leg,
|
||||
quote_fill_quantity,
|
||||
hedge_leg);
|
||||
}
|
||||
////////////////////////////////////////////////////
|
||||
// Rounding functions //////////////////////////////
|
||||
////////////////////////////////////////////////////
|
||||
// rounds a double to a whole number if its too close
|
||||
// to it (this is ued in the case of rounding errors
|
||||
// when 0.9999999 or 1.0000001 is really 1)
|
||||
static double round_if_near_whole(double qty,
|
||||
double vicinity=0.000000001){
|
||||
double round_qty = std::round(qty);
|
||||
if (std::abs(round_qty - qty) <= vicinity)
|
||||
return round_qty;
|
||||
else
|
||||
return qty;
|
||||
}
|
||||
static uint32_t _round_quantity(double qty, bool is_up){
|
||||
double round_err = 0.000000001;
|
||||
double round_qty = std::round(qty);
|
||||
|
||||
+1
-2
@@ -10,8 +10,7 @@ namespace orderman {
|
||||
class SpreaderChildOrderState;
|
||||
class Book;
|
||||
class SingleFillHedger;
|
||||
class IHedgerStateWorker
|
||||
{
|
||||
class IHedgerStateWorker{
|
||||
public:
|
||||
// called once when transition to this state happens
|
||||
virtual void on_transition () = 0;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "UnhedgedDelta.h"
|
||||
#include <cmath>
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
|
||||
|
||||
namespace bts{
|
||||
namespace orderman{
|
||||
|
||||
UnhedgedDelta::UnhedgedDelta()
|
||||
:
|
||||
_qty(0),
|
||||
_avg_price(0){
|
||||
}
|
||||
void UnhedgedDelta::add_unhedged(double price,double qty){
|
||||
bts_assert(qty >= 0);
|
||||
auto total_qty=_qty+qty;
|
||||
_avg_price = (_qty*_avg_price + qty*price)/total_qty;
|
||||
_qty=total_qty;
|
||||
}
|
||||
uint32_t UnhedgedDelta::subtract_hedge_if_available(){
|
||||
uint32_t rv = 0;
|
||||
double qty = SpreaderUtils::round_if_near_whole(_qty);
|
||||
if (qty >= 1){
|
||||
rv = std::floor(qty);
|
||||
_qty = qty-rv;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace bts{
|
||||
namespace orderman{
|
||||
// in the case when the hedge quantity is not a full contract
|
||||
// (ex if we get a 1 lot fill on a 1 X 3 spreader, where 3 is
|
||||
// a quoting leg), this class is used to accumulate the unhedged
|
||||
// delta
|
||||
class UnhedgedDelta{
|
||||
private:
|
||||
double _qty;
|
||||
double _avg_price;
|
||||
public:
|
||||
UnhedgedDelta();
|
||||
// adds to the unhedged qty and updates the
|
||||
// running average price
|
||||
void add_unhedged (double price, double qty);
|
||||
// if >= 1 unhedged delta is available, it
|
||||
// subtracts the integer part and returns it.
|
||||
// else returns 0
|
||||
uint32_t subtract_hedge_if_available();
|
||||
};
|
||||
}}
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "QuoteDriverSet.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
QuoteDriverSet::QuoteDriverSet(size_t n_legs)
|
||||
:
|
||||
_quote_seq_no(0),
|
||||
_quote_price(0.0),
|
||||
_drivers(){
|
||||
_drivers.reserve(n_legs);
|
||||
for (size_t i=0; i<n_legs; i++)
|
||||
_drivers.emplace_back();
|
||||
}
|
||||
void QuoteDriverSet::update(uint16_t quote_seq_no,
|
||||
Price quote_price,
|
||||
const std::vector<QuoteLegDriver>& drivers){
|
||||
_quote_seq_no=quote_seq_no;
|
||||
_quote_price=quote_price;
|
||||
bts_assert(_drivers.size()==drivers.size());
|
||||
int i=0;
|
||||
for(const auto& d:drivers)
|
||||
_drivers[i++]=d;
|
||||
}
|
||||
const QuoteLegDriver& QuoteDriverSet::leg_driver(size_t leg_index) const{
|
||||
bts_assert(leg_index<_drivers.size());
|
||||
return _drivers[leg_index];
|
||||
}
|
||||
}}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "QuoteLegDriver.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
// this is the list of all drivers for a
|
||||
// single quoting leg
|
||||
class QuoteDriverSet{
|
||||
private:
|
||||
uint16_t _quote_seq_no;
|
||||
Price _quote_price;
|
||||
std::vector<QuoteLegDriver> _drivers;
|
||||
public:
|
||||
QuoteDriverSet (size_t n_legs);
|
||||
uint16_t quote_seq_no() const{
|
||||
return _quote_seq_no;
|
||||
}
|
||||
Price quote_price() const{
|
||||
return _quote_price;
|
||||
}
|
||||
void update(uint16_t quote_seq_no,
|
||||
Price quote_price,
|
||||
const std::vector<QuoteLegDriver>& drivers);
|
||||
const QuoteLegDriver& leg_driver (size_t leg_index) const;
|
||||
};
|
||||
}}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
#include "QuoteDriverSetHistory.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
QuoteDriverSetHistory::QuoteDriverSetHistory(size_t n_legs,
|
||||
size_t cache_size,
|
||||
double tick_size)
|
||||
:
|
||||
_n_legs(n_legs),
|
||||
_cache_size(cache_size),
|
||||
_tick_size(tick_size),
|
||||
_quote_seq_no(0),
|
||||
_driver_sets(),
|
||||
_h_ptr(0){
|
||||
_driver_sets.reserve(cache_size);
|
||||
for(size_t i=0;i<cache_size;i++)
|
||||
_driver_sets.emplace_back(n_legs);
|
||||
}
|
||||
void QuoteDriverSetHistory::add(const std::vector<QuoteLegDriver>& drivers,
|
||||
Price quote_price){
|
||||
bts_assert(_h_ptr<_cache_size);
|
||||
bts_assert(_cache_size>0);
|
||||
_quote_seq_no++;
|
||||
_driver_sets[_h_ptr].update(_quote_seq_no,quote_price,drivers);
|
||||
// move it to the next writeable place
|
||||
if(_h_ptr == _cache_size-1)
|
||||
_h_ptr = 0;
|
||||
else
|
||||
_h_ptr++;
|
||||
}
|
||||
const QuoteDriverSet* QuoteDriverSetHistory::_latest() const{
|
||||
if (_quote_seq_no>0)
|
||||
return &(_driver_sets[_h_ptr==0 ? _cache_size-1 : _h_ptr-1]);
|
||||
return
|
||||
nullptr;
|
||||
}
|
||||
const QuoteDriverSet* QuoteDriverSetHistory::_find_driver_set(Price quote_price) const{
|
||||
const QuoteDriverSet* rv=nullptr;
|
||||
for(const auto& d : _driver_sets)
|
||||
if (bts::securities::test_price_equality(d.quote_price().value(),
|
||||
quote_price.value(),
|
||||
_tick_size))
|
||||
if (rv==nullptr||d.quote_seq_no()>rv->quote_seq_no())
|
||||
rv=&d;
|
||||
return rv!=nullptr ? rv:_latest();
|
||||
}
|
||||
const QuoteLegDriver* QuoteDriverSetHistory::find_driver(Price quote_price,
|
||||
size_t leg_index) const{
|
||||
const QuoteDriverSet* driver_set_ptr=_find_driver_set(quote_price);
|
||||
return driver_set_ptr==nullptr ? nullptr : &(driver_set_ptr->leg_driver(leg_index));
|
||||
}
|
||||
}}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "QuoteDriverSet.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class QuoteDriverSetHistory{
|
||||
private:
|
||||
const size_t _n_legs, _cache_size;
|
||||
const double _tick_size;
|
||||
uint16_t _quote_seq_no;
|
||||
std::vector<QuoteDriverSet> _driver_sets;
|
||||
uint16_t _h_ptr;
|
||||
const QuoteDriverSet* _latest() const;
|
||||
const QuoteDriverSet* _find_driver_set(Price quote_price) const;
|
||||
public:
|
||||
QuoteDriverSetHistory(size_t n_legs,
|
||||
size_t cache_size,
|
||||
double tick_size);
|
||||
void add(const std::vector<QuoteLegDriver>& drivers,
|
||||
Price quote_price);
|
||||
const QuoteLegDriver* find_driver(Price quote_price, size_t leg_index) const;
|
||||
};
|
||||
|
||||
}}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "QuoteLegDriver.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
QuoteLegDriver::QuoteLegDriver()
|
||||
:
|
||||
_driver_price(0.0),
|
||||
_driver_quantity(0),
|
||||
_quoter_leg_quantity(0){
|
||||
}
|
||||
}}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "server/sparkd/orderman/orderman_utils/orderman_common.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
// Should be read as Quote_LegDriver, this is the
|
||||
// price and qty in a hedging leg that can sustain
|
||||
// a "_quoter_leg_quantity" sized quote in the
|
||||
// quoting leg
|
||||
class QuoteLegDriver
|
||||
{
|
||||
private:
|
||||
Price _driver_price;
|
||||
Quantity _driver_quantity;
|
||||
Quantity _quoter_leg_quantity;
|
||||
public:
|
||||
// driver_price and driver_quantity are the price and quantity
|
||||
// in the hedging (or driver) leg that can drive a quoter_leg_quantity
|
||||
// sized order in the quoting leg
|
||||
QuoteLegDriver();
|
||||
QuoteLegDriver& operator=(const QuoteLegDriver& s){
|
||||
set(s.driver_price(),s.driver_quantity(), s.quoter_leg_quantity());
|
||||
return *this;
|
||||
}
|
||||
void set(Price driver_price,
|
||||
Quantity driver_quantity,
|
||||
Quantity quoter_leg_quantity){
|
||||
_driver_price = driver_price;
|
||||
_driver_quantity = driver_quantity;
|
||||
_quoter_leg_quantity = quoter_leg_quantity;
|
||||
}
|
||||
Price driver_price() const{
|
||||
return _driver_price;
|
||||
}
|
||||
Quantity driver_quantity() const{
|
||||
return _driver_quantity;
|
||||
}
|
||||
Quantity quoter_leg_quantity() const{
|
||||
return _quoter_leg_quantity;
|
||||
}
|
||||
};
|
||||
}}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "server/sparkd/orderman/orderman_utils/orderman_common.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
// the concept of a "driver" is the size and price of a leg that
|
||||
// can sustain a quote in the quoted leg
|
||||
class QuoterLegDriverData
|
||||
{
|
||||
public:
|
||||
// driver_price and driver_quantity are the price and quantity
|
||||
// in the hedging (or driver) leg that can drive a quoter_leg_quantity
|
||||
// sized order in the quoting leg
|
||||
Price driver_price;
|
||||
Quantity driver_quantity;
|
||||
Quantity quoter_leg_quantity;
|
||||
|
||||
QuoterLegDriverData()
|
||||
:
|
||||
driver_price(0.0),
|
||||
driver_quantity(0),
|
||||
quoter_leg_quantity(0)
|
||||
{}
|
||||
void set(Price new_driver_price, Quantity new_driver_quantity, Quantity new_quoter_leg_quantity)
|
||||
{
|
||||
driver_price = new_driver_price;
|
||||
driver_quantity = new_driver_quantity;
|
||||
quoter_leg_quantity = new_quoter_leg_quantity;
|
||||
}
|
||||
};
|
||||
}}
|
||||
+1
-1
@@ -51,7 +51,7 @@ void QuoterWaitingForSizeStateWorker::_longpath_analyze_and_act (orderman::TickT
|
||||
Price out_price; Quantity out_quantity;
|
||||
_quoter->_compute_price_quantity (out_price, out_quantity);
|
||||
if (out_quantity.value() > 0){
|
||||
_quoter->_commit_drivers ();
|
||||
_quoter->_commit_drivers (out_price);
|
||||
LOG(OrderMan, Info, "QuoterWaitingForSizeStateWorker sending order key: {} price: {} quantity: {}", _quoter->_key(), out_price, out_quantity);
|
||||
// this is the only place where a quoter order is entered
|
||||
_quoter->_place_order(out_price, out_quantity, cid);
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ void QuoterWorkingStateWorker::_modify_order (orderman::TickToTradeCorID cid, Pr
|
||||
bts::securities::test_price_equality(_quoter->_price_quantity_state.out_price().value(),
|
||||
price.value(), _quoter->_this_leg_data.tick_size());
|
||||
if (!same_price_qty){
|
||||
_quoter->_commit_drivers ();
|
||||
_quoter->_commit_drivers (price);
|
||||
_quoter->_modify_order(price, quantity, cid);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-4
@@ -40,10 +40,13 @@ void SpreaderStartStateWorker::on_init_recovered(const bts::api::orderman::Order
|
||||
const SpreaderPreActivationOrderServices& spreader_pao_services)
|
||||
{
|
||||
bts_assert(!_spreader->_inited());
|
||||
// we are not handling recovery, so we go directly to the end state
|
||||
// and the status of the order to cancelled
|
||||
bts::api::orderman::OrderRecord order_record_copy = order_record;
|
||||
order_record_copy.mutable_state()->set_status(bts::api::orderman::OrderStatus::ORDER_STATUS_CANCELED);
|
||||
// we are not handling recovery, so if order is not in a terminal state,
|
||||
// instead of carrying on from where we left, we go directly to the end state
|
||||
// and mark the order cancelled. if its already in a terminal state we
|
||||
// just go to end and not do anything
|
||||
bts::api::orderman::OrderRecord order_record_copy = order_record;
|
||||
if (!SpreaderUtils::order_in_terminal_state(order_record_copy.mutable_state()->status()))
|
||||
order_record_copy.mutable_state()->set_status(bts::api::orderman::OrderStatus::ORDER_STATUS_CANCELED);
|
||||
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader,
|
||||
order_record_copy,
|
||||
spreader_pao_services,
|
||||
|
||||
+24
-12
@@ -97,19 +97,31 @@ void SpreaderWorkingStateWorker::on_quoter_order_fill(SpreaderLegData& leg_data)
|
||||
order_update_message.last_update_filled_quantity(),
|
||||
order_update_message.last_update_filled_price());
|
||||
bts_assert (order_update_message.last_update_filled_quantity().value() > 0);
|
||||
for (size_t q_idx = 0; q_idx < _spreader->_bookkeeping_data().n_legs(); q_idx++){
|
||||
SpreaderLegData& other_leg_data = _spreader->_bookkeeping_data().get_leg_data (q_idx);
|
||||
for (size_t q_idx = 0; q_idx < _spreader->_bookkeeping_data().n_legs(); q_idx++){
|
||||
if (q_idx != leg_data.leg_property_data().leg_index()){
|
||||
// if it is an actively quoting leg, inform the quoter about it
|
||||
// so it can decrease the proportional size
|
||||
if (other_leg_data.leg_property_data().is_active_quoting())
|
||||
other_leg_data.quoter().update_other_leg_fills(leg_data.leg_property_data().leg_index(),
|
||||
order_update_message.last_update_filled_quantity());
|
||||
// a hedge is sent regardless
|
||||
_spreader->_send_hedge (leg_data.leg_property_data().leg_index(),
|
||||
order_update_message.last_update_filled_price(),
|
||||
order_update_message.last_update_filled_quantity(),
|
||||
q_idx);
|
||||
SpreaderLegData& other_leg_data = _spreader->_bookkeeping_data().get_leg_data (q_idx);
|
||||
Price hedge_price;
|
||||
double hedge_qty;
|
||||
_spreader->_compute_hedge_price_quantity(leg_data.leg_property_data().leg_index(),
|
||||
order_update_message.last_update_filled_price(),
|
||||
order_update_message.last_update_filled_quantity().value(),
|
||||
q_idx,
|
||||
hedge_price,
|
||||
hedge_qty);
|
||||
UnhedgedDelta& leg_unhedged_delta = other_leg_data.unhedged_delta();
|
||||
leg_unhedged_delta.add_unhedged(hedge_price.value(), hedge_qty);
|
||||
uint32_t total_unhedged_qty=leg_unhedged_delta.subtract_hedge_if_available();
|
||||
if (total_unhedged_qty != 0){
|
||||
// if it is an actively quoting leg, inform the quoter about it
|
||||
// so it can decrease the proportional size
|
||||
if (other_leg_data.leg_property_data().is_active_quoting())
|
||||
other_leg_data.quoter().update_other_leg_fills(leg_data.leg_property_data().leg_index(),
|
||||
Quantity(total_unhedged_qty));
|
||||
// a hedge is sent regardless
|
||||
_spreader->_send_hedge(other_leg_data.leg_property_data().leg_index(),
|
||||
hedge_price,
|
||||
Quantity(total_unhedged_qty));
|
||||
}
|
||||
}
|
||||
}
|
||||
// this is done at the end (i dont like ordering though) because
|
||||
|
||||
@@ -483,7 +483,7 @@ TEST_CASE ("compute quoting price quantity 3 legs", "[spreader]")
|
||||
std::vector<LegSpreaderParams> params; params.push_back(leg_params_1);params.push_back(leg_params_2);params.push_back(leg_params_3);
|
||||
|
||||
SpreaderManagerForTest* sm = create_spreader_manager_n_legs (OrderKey(), legs, params);
|
||||
std::vector<QuoterLegDriverData> driver_vector;
|
||||
std::vector<QuoteLegDriver> driver_vector;
|
||||
for(size_t i = 0; i < legs.size(); i++) driver_vector.emplace_back();
|
||||
|
||||
SECTION("basic joining")
|
||||
@@ -570,8 +570,9 @@ TEST_CASE ("compute quoting price quantity 3 legs", "[spreader]")
|
||||
TEST_CASE ("compute hedge price quantity 2 legs", "[spreader]")
|
||||
{
|
||||
ContractLeg leg_1, leg_2;
|
||||
Price out_hedge_price; Quantity out_hedge_quantity;
|
||||
Price spreader_price = Price (0.35);
|
||||
Price out_hedge_price;
|
||||
double out_hedge_quantity;
|
||||
Price spreader_price = Price(0.35);
|
||||
double size_mult_1 = 0.25, size_mult_2 = -1.1, // same used for price and qty
|
||||
price_mult_1 = 0.35, price_mult_2 = -1.5;
|
||||
double lean_mult = 1.0;
|
||||
@@ -580,23 +581,23 @@ TEST_CASE ("compute hedge price quantity 2 legs", "[spreader]")
|
||||
LegPropertyData leg_prop_data_1 = create_leg_property_data (leg_1, 0, price_mult_1, true, lean_mult);
|
||||
LegPropertyData leg_prop_data_2 = create_leg_property_data (leg_2, 1, price_mult_2, true, lean_mult);
|
||||
// md
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_2, Price (2500.5), Quantity (3),
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_2, Price(2500.5), 3,
|
||||
leg_prop_data_1, out_hedge_price, out_hedge_quantity);
|
||||
REQUIRE (out_hedge_quantity.value() == 1);
|
||||
REQUIRE (std::abs(out_hedge_quantity - 0.6818181818181818181) < 0.000000001);
|
||||
REQUIRE (out_hedge_price.value() == 10717.25);
|
||||
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_2, Price (2500.5), Quantity (2),
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_2, Price(2500.5), 2,
|
||||
leg_prop_data_1, out_hedge_price, out_hedge_quantity);
|
||||
REQUIRE (out_hedge_quantity.value() == 0);
|
||||
REQUIRE (std::abs(out_hedge_quantity - 0.45454545454545454545) < 0.000000001);
|
||||
REQUIRE (out_hedge_price.value() == 10717.25);
|
||||
// reversing the side of the quote fill
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_1, Price (10000.5), Quantity (2),
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_1, Price(10000.5), 2,
|
||||
leg_prop_data_2, out_hedge_price, out_hedge_quantity);
|
||||
REQUIRE (out_hedge_quantity.value() == 9);
|
||||
REQUIRE (std::abs(out_hedge_quantity - 8.8)<0.000000001);
|
||||
REQUIRE (out_hedge_price.value() == 2333.25);
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_1, Price (9997), Quantity (6),
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_1, Price(9997), 6,
|
||||
leg_prop_data_2, out_hedge_price, out_hedge_quantity);
|
||||
REQUIRE (out_hedge_quantity.value() == 26);
|
||||
REQUIRE (std::abs(out_hedge_quantity - 26.40) < 0.000000001);
|
||||
REQUIRE (out_hedge_price.value() == 2332.5);
|
||||
}
|
||||
/************************************************************************************************************/
|
||||
@@ -2341,6 +2342,201 @@ TEST_CASE ("prehedge_payup", "[spreader]"){
|
||||
REQUIRE(bts::securities::test_price_equality(out_price.value(),21.10,0.05));
|
||||
}
|
||||
}
|
||||
// n legged spreader price evaluation /////////////////////
|
||||
///////////////////////////////////////////////////////////
|
||||
TEST_CASE ("nlegs_compute_driver", "[spreader]"){
|
||||
bts::orderman::OrderKey order_key(1);
|
||||
ContractLeg leg_1, leg_2, leg_3, leg_4;
|
||||
fill_future_contract_leg (leg_1, 1, "CL", bts::api::Venue::VENUE_GLBX, 2018, 1, 15, 1.0, 1.0, api::Side::SIDE_BUY, 1.0, 0.01);
|
||||
fill_future_contract_leg (leg_2, 2, "CL", bts::api::Venue::VENUE_GLBX, 2018, 2, 15, 1.0, 1.0, api::Side::SIDE_BUY, 1.0, 0.01);
|
||||
fill_future_contract_leg (leg_3, 3, "CL", bts::api::Venue::VENUE_GLBX, 2018, 3, 15, 1.0, 1.0, api::Side::SIDE_BUY, 1.0, 0.01);
|
||||
fill_future_contract_leg (leg_4, 4, "CL", bts::api::Venue::VENUE_GLBX, 2018, 4, 15, -3.0, -3.0, api::Side::SIDE_SELL, 1.0, 0.01);
|
||||
std::vector<ContractLeg> legs;
|
||||
legs.push_back(leg_1);legs.push_back(leg_2);legs.push_back(leg_3);legs.push_back(leg_4);
|
||||
Security sec;
|
||||
fill_synthetic_security (sec, 2000, "4legs", legs);
|
||||
// execution params/order def
|
||||
LegSpreaderParams lsp_1, lsp_2, lsp_3, lsp_4;
|
||||
fill_leg_spreader_params(lsp_1, 1.0, false, 1.0);fill_leg_spreader_params(lsp_2, 1.0, false, 1.0);
|
||||
fill_leg_spreader_params(lsp_3, 1.0, false, 1.0);fill_leg_spreader_params(lsp_4, -4.0, true, 1.0);
|
||||
// // post hedge time out here
|
||||
std::vector<LegSpreaderParams> params;
|
||||
params.push_back(lsp_1);params.push_back(lsp_2);
|
||||
params.push_back(lsp_3);params.push_back(lsp_4);
|
||||
bts::api::orderman::OrderDefinition order_def;
|
||||
fill_spreader_order_definition(order_def, Side::SIDE_BUY, sec, params);
|
||||
// putting it together in SpreaderBookkeepingData
|
||||
SpreaderPreActivationOrderServices::TestOnlySpreaderPreActivationOrderServices
|
||||
testonly_spreader_pao_services = _create_spreader_pao_services(sec);
|
||||
SpreaderPreActivationOrderServices pao_services (&testonly_spreader_pao_services);
|
||||
std::vector<LegPropertyData> leg_props;
|
||||
SpreaderUtils::resolve_synthetic_spread(pao_services, order_def, leg_props);
|
||||
SpreaderManagerForTest spreader_manager (order_key, order_def, pao_services, leg_props);
|
||||
SpreaderBookkeepingData& bookkeeping_data = spreader_manager.bookkeeping_data();
|
||||
SECTION("compute_1"){
|
||||
Quantity quoting_leg_target (300);
|
||||
Price spread_price(0.25);
|
||||
fill_book(spreader_manager, 0, new double[5]{65.31,65.32,65.34,65.36, 65.40},
|
||||
new uint16_t[5]{12,18,20,19,23},bts::api::Side::SIDE_SELL,true);
|
||||
fill_book(spreader_manager, 1, new double[5]{64.40,64.41,64.43,64.45,64.50},
|
||||
new uint16_t[5]{1,5,20,12,15},bts::api::Side::SIDE_SELL,true);
|
||||
fill_book(spreader_manager, 2, new double[5]{64.10,64.18,64.19,64.20,64.21},
|
||||
new uint16_t[5]{10,43,17,50,15},bts::api::Side::SIDE_SELL,true);
|
||||
std::vector<QuoteLegDriver> out_driver_vector;
|
||||
for (int i=0; i<4; i++)
|
||||
out_driver_vector.emplace_back();
|
||||
SpreaderUtils::compute_drivers(quoting_leg_target, 3, spreader_manager, out_driver_vector);
|
||||
_verify_driver (out_driver_vector[0],92,65.40,276);
|
||||
_verify_driver (out_driver_vector[1],53,64.50,159);
|
||||
_verify_driver (out_driver_vector[2],100,64.20,300);
|
||||
// size of the quoting leg
|
||||
Quantity computed_quoted_qty=SpreaderUtils::compute_quoting_quantity_from_drivers(3,out_driver_vector);
|
||||
REQUIRE(computed_quoted_qty.value()==159);
|
||||
// 2nd pass drivers
|
||||
SpreaderUtils::compute_drivers(computed_quoted_qty, 3, spreader_manager, out_driver_vector);
|
||||
_verify_driver (out_driver_vector[0],53,65.36,159);
|
||||
_verify_driver (out_driver_vector[1],53,64.50,159);
|
||||
_verify_driver (out_driver_vector[2],53,64.18,159);
|
||||
// price of the quoting leg
|
||||
Price computed_price=SpreaderUtils::compute_quoting_price_from_drivers(spread_price,3,spreader_manager,out_driver_vector);
|
||||
REQUIRE(computed_price.value()==64.60);
|
||||
//hedging
|
||||
Price hedge_price; double hedge_quantity;
|
||||
SpreaderUtils::compute_hedge_price_size_n_legs(spreader_manager.get_leg_data(3),
|
||||
Price(0),
|
||||
18,
|
||||
spreader_manager.get_leg_data(0),
|
||||
out_driver_vector[0],
|
||||
hedge_price,
|
||||
hedge_quantity);
|
||||
REQUIRE(hedge_price.value()==65.36);
|
||||
REQUIRE(hedge_quantity==6);
|
||||
}
|
||||
SECTION("compute_2_fractional"){
|
||||
Quantity quoting_leg_target (100);
|
||||
Price spread_price(0.25);
|
||||
fill_book(spreader_manager, 0, new double[5]{65.31,65.32,65.34,65.36, 65.40},
|
||||
new uint16_t[5]{12,18,20,19,23},bts::api::Side::SIDE_SELL,true);
|
||||
fill_book(spreader_manager, 1, new double[5]{64.40,64.41,64.43,64.45,64.50},
|
||||
new uint16_t[5]{1,5,20,12,15},bts::api::Side::SIDE_SELL,true);
|
||||
fill_book(spreader_manager, 2, new double[5]{64.10,64.18,64.19,64.20,64.21},
|
||||
new uint16_t[5]{11,1,1,5,1},bts::api::Side::SIDE_SELL,true);
|
||||
std::vector<QuoteLegDriver> out_driver_vector;
|
||||
for (int i=0; i<4; i++)
|
||||
out_driver_vector.emplace_back();
|
||||
SpreaderUtils::compute_drivers(quoting_leg_target, 3, spreader_manager, out_driver_vector);
|
||||
_verify_driver (out_driver_vector[0],34,65.34,100);
|
||||
_verify_driver (out_driver_vector[1],34,64.45,100);
|
||||
_verify_driver (out_driver_vector[2],19,64.21,57);
|
||||
// size of the quoting leg
|
||||
Quantity computed_quoted_qty=SpreaderUtils::compute_quoting_quantity_from_drivers(3,out_driver_vector);
|
||||
REQUIRE(computed_quoted_qty.value()==57);
|
||||
// // 2nd pass drivers
|
||||
SpreaderUtils::compute_drivers(computed_quoted_qty, 3, spreader_manager, out_driver_vector);
|
||||
_verify_driver (out_driver_vector[0],19,65.32,57);
|
||||
_verify_driver (out_driver_vector[1],19,64.43,57);
|
||||
_verify_driver (out_driver_vector[2],19,64.21,57);
|
||||
// // price of the quoting leg
|
||||
Price computed_price=SpreaderUtils::compute_quoting_price_from_drivers(spread_price,3,spreader_manager,out_driver_vector);
|
||||
REQUIRE(computed_price.value()==64.57);
|
||||
// //hedging
|
||||
Price hedge_price; double hedge_quantity;
|
||||
SpreaderUtils::compute_hedge_price_size_n_legs(spreader_manager.get_leg_data(3),
|
||||
Price(0),
|
||||
10.0,
|
||||
spreader_manager.get_leg_data(0),
|
||||
out_driver_vector[0],
|
||||
hedge_price,
|
||||
hedge_quantity);
|
||||
REQUIRE(hedge_price.value()==65.32);
|
||||
REQUIRE(std::abs(hedge_quantity - 3.333333) < 0.000001);
|
||||
}
|
||||
}
|
||||
TEST_CASE ("quote driver caching", "[spreader]"){
|
||||
SECTION("zero_search"){
|
||||
QuoteDriverSetHistory driver_cache(5,10,0.05);
|
||||
const QuoteLegDriver* driver_ptr=driver_cache.find_driver(Price(100),3);
|
||||
REQUIRE(driver_ptr==nullptr);
|
||||
}
|
||||
SECTION("simple_search"){
|
||||
size_t n_legs=5, cache_sz=10;
|
||||
QuoteDriverSetHistory driver_cache(n_legs,cache_sz,0.05);
|
||||
Price p1(100); std::vector<QuoteLegDriver> d1;
|
||||
generate_driver_set(d1,n_legs);
|
||||
driver_cache.add(d1,p1);
|
||||
Price p2(100.05); std::vector<QuoteLegDriver> d2;
|
||||
generate_driver_set(d2,n_legs);
|
||||
driver_cache.add(d2,p2);
|
||||
Price p3(100.10); std::vector<QuoteLegDriver> d3;
|
||||
generate_driver_set(d3,n_legs);
|
||||
driver_cache.add(d3,p3);
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p1,i);
|
||||
verify_driver(*d,d1[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p2,i);
|
||||
verify_driver(*d,d2[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p3,i);
|
||||
verify_driver(*d,d3[i]);
|
||||
}
|
||||
// if not found, returns the latest
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(Price(100.01),i);
|
||||
verify_driver(*d,d3[i]);
|
||||
}
|
||||
}
|
||||
SECTION("collision_search and continous add"){
|
||||
// adding multiple updates to test circular nature
|
||||
size_t n_runs=10003, n_legs=4, cache_sz=10;
|
||||
QuoteDriverSetHistory driver_cache(n_legs,cache_sz,0.05);
|
||||
for(size_t i=0; i<n_runs; i++){
|
||||
Price p(gen_rand(1,100));
|
||||
std::vector<QuoteLegDriver> d;
|
||||
generate_driver_set(d,n_legs);
|
||||
driver_cache.add(d,p);
|
||||
}
|
||||
Price p1(100); std::vector<QuoteLegDriver> d1;
|
||||
generate_driver_set(d1,n_legs);
|
||||
driver_cache.add(d1,p1);
|
||||
Price p2(100.05); std::vector<QuoteLegDriver> d2;
|
||||
generate_driver_set(d2,n_legs);
|
||||
driver_cache.add(d2,p2);
|
||||
Price p3(100.10); std::vector<QuoteLegDriver> d3;
|
||||
generate_driver_set(d3,n_legs);
|
||||
driver_cache.add(d3,p3);
|
||||
Price p4(100); std::vector<QuoteLegDriver> d4;
|
||||
generate_driver_set(d4,n_legs);
|
||||
driver_cache.add(d4,p4);
|
||||
Price p5(100.15); std::vector<QuoteLegDriver> d5;
|
||||
generate_driver_set(d5,n_legs);
|
||||
driver_cache.add(d5,p5);
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p1,i);
|
||||
verify_driver(*d,d4[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p2,i);
|
||||
verify_driver(*d,d2[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p3,i);
|
||||
verify_driver(*d,d3[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p4,i);
|
||||
verify_driver(*d,d4[i]);
|
||||
}
|
||||
// 100 is there twice, should returns the
|
||||
// most recently added one
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p5,i);
|
||||
verify_driver(*d,d5[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
class RV
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -292,6 +292,20 @@ bts::orderman::Book get_book(SecurityKey key,
|
||||
{
|
||||
return get_book(key, n_levels, bid_prices, bid_sizes, n_levels, ask_prices, ask_sizes, delete_arrays);
|
||||
}
|
||||
void fill_book (SpreaderManagerForTest& sm,
|
||||
size_t index,
|
||||
double* prices,
|
||||
uint16_t* sizes,
|
||||
bts::api::Side side,
|
||||
bool delete_arrays){
|
||||
Book book;
|
||||
fill_book (book, prices, sizes, side);
|
||||
sm.set_book(book, index);
|
||||
if (delete_arrays){
|
||||
delete[] prices;
|
||||
delete[] sizes;
|
||||
}
|
||||
}
|
||||
/**********************************************************************/
|
||||
/******************************hedger**********************************/
|
||||
/**********************************************************************/
|
||||
@@ -464,14 +478,38 @@ void set_spreader_orders (SpreaderOrder& spreader, double* leg_1_bid_prices, uin
|
||||
/**********************************************************************/
|
||||
/******************************misc************************************/
|
||||
/**********************************************************************/
|
||||
void _verify_driver(const QuoteLegDriver& driver_data,
|
||||
uint16_t driver_qty,
|
||||
double driver_price,
|
||||
uint16_t quoter_leg_quantity){
|
||||
double err = 1e-9;
|
||||
REQUIRE(std::abs(driver_data.driver_price().value() - driver_price)< err);
|
||||
REQUIRE(driver_data.driver_quantity().value() == driver_qty);
|
||||
REQUIRE(driver_data.quoter_leg_quantity().value() == quoter_leg_quantity);
|
||||
}
|
||||
void verify_driver(const QuoteLegDriver& d1,
|
||||
const QuoteLegDriver& d2){
|
||||
_verify_driver(d1,
|
||||
d2.driver_quantity().value(),
|
||||
d2.driver_price().value(),
|
||||
d2.quoter_leg_quantity().value());
|
||||
}
|
||||
double gen_rand (double min, double max)
|
||||
{
|
||||
double f = (double)rand() / RAND_MAX;
|
||||
return min + f * (max - min);
|
||||
}
|
||||
// SpreaderChildOrderState create_order_update(const LegPropertyData& leg_data)
|
||||
// {
|
||||
// SpreaderChildOrderState rv(false, leg_data, true);
|
||||
// return rv;
|
||||
// }
|
||||
}}
|
||||
void fill_random_driver(QuoteLegDriver& driver){
|
||||
driver.set(Price(gen_rand(0,10)),
|
||||
Quantity(gen_rand(1,100)),
|
||||
Quantity(gen_rand(1,300)));
|
||||
}
|
||||
void generate_driver_set(std::vector<QuoteLegDriver>& drivers, size_t n_legs){
|
||||
drivers.clear();
|
||||
for(size_t i=0; i < n_legs; i++){
|
||||
QuoteLegDriver d;
|
||||
fill_random_driver(d);
|
||||
drivers.push_back(d);
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -110,7 +110,7 @@ OrderServiceServer::~OrderServiceServer() {
|
||||
}
|
||||
|
||||
void OrderServiceServer::on_orders(
|
||||
std::vector<api::orderman::OrderRecord const *> const &orders) {
|
||||
std::vector<api::orderman::OrderRecord> const &orders) {
|
||||
LOG(OrderMan, Info, "Received order update for publishing");
|
||||
std::vector<api::orderman::OrderRecord> created;
|
||||
created.reserve(orders.size());
|
||||
@@ -118,12 +118,12 @@ void OrderServiceServer::on_orders(
|
||||
updated.reserve(orders.size());
|
||||
|
||||
for (auto const &rec : orders) {
|
||||
auto const &k = OrderKey(rec->key());
|
||||
auto const &k = OrderKey(rec.key());
|
||||
if (me->known_orders.count(k) == 0) {
|
||||
me->known_orders.insert(k);
|
||||
created.push_back(*rec);
|
||||
created.push_back(rec);
|
||||
} else
|
||||
updated.push_back(*rec);
|
||||
updated.push_back(rec);
|
||||
}
|
||||
|
||||
LOG(OrderMan, Info,
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
private:
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
void on_orders(std::vector<api::orderman::OrderRecord const*> const&);
|
||||
void on_orders(std::vector<api::orderman::OrderRecord> const&);
|
||||
void on_fills (std::vector<api::orderman::Fill const*> const&);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
This test verifies that fractional hedges work. That is, when a fill in a quoting leg does
|
||||
not equate to one full contract in the hedging leg, we accumulate the unhedged
|
||||
delta, until we can send one or more hedging contracts.
|
||||
|
||||
1. Create a new synthetic spread. For the legs choose SIF9 and SIH9 (if you dont
|
||||
have these contracts, other contracts can be chosen too) and for the quantities choose 1 and -1.5
|
||||
respectively (and same for the Price multiplier). Disable all quoting, pre-hedge and post-hedge
|
||||
parameters. Name the synthetic SIF9-1.5SIH9.
|
||||
|
||||
1. Bring up the ladder for the synthetic and for the 2 legs.
|
||||
|
||||
2. Enter a 10 lot sell order at 20.0 for SIF9 (If the ladder is showing blanks use the market view
|
||||
to enter orders). The quoting order will lean against this.
|
||||
|
||||
3. Enter a 10 lot buy order for the synthetic at -9.900.
|
||||
|
||||
4. Verify that a 15 lot quoting order is sent for SIH9 at 19.935.
|
||||
|
||||
5. Now we will start filling the quoting order and verify the hedges. A 1 lot fill in the
|
||||
quoting leg means a 2/3 contract in the hedging leg. Since we can not send 2/3 constracts,
|
||||
we will add it to the "unhedged delta" till the unhedged delta > 1, and then we will send one
|
||||
or more orders to get the unhedged delta < 1.
|
||||
|
||||
Force quoting fills based on the left most column (by lifting the quoting offer)
|
||||
and verify that hedge orders of size "hedge_qty" (3rd column) are sent as a result.
|
||||
The "unhedged_delta" column shows the unhedged delta immediatly after the quoting fill
|
||||
and the "remaining_unhedged_delta" column is the unhedged delta after the hedge order
|
||||
has been sent.
|
||||
You can check the hedge orders being sent easily by looking at the order reduction
|
||||
happening on the order sent in step 2 (or look at the hedges in the orderbook).
|
||||
------------------------------------------------------------------------------
|
||||
quoting fill unhedged_delta hedge_order_sent remaining_unhedged_delta
|
||||
------------------------------------------------------------------------------
|
||||
1 2/3 0 2/3
|
||||
1 4/3 1 1/3
|
||||
1 1 1 0
|
||||
4 8/3 2 2/3
|
||||
5 4 4 0
|
||||
2 4/3 1 1/3
|
||||
1 1 1 0
|
||||
|
||||
6. At the end of this, the whole order should be filled. Check the order book
|
||||
to make sure the fill price is -9.900, there are 10 total fills in SIF9 and
|
||||
15 fills in SIH9.
|
||||
@@ -0,0 +1,34 @@
|
||||
This test makes sure the basic spreader functionality works for 4 legged synthetic spreads
|
||||
with 1 quoting leg.
|
||||
|
||||
0. Run this test in sim, with no external markets. We will control the markets with limit orders.
|
||||
|
||||
1. Create a 4 legged synthetic spread with legs SIF9, SIH9, SIK9, SIN9, with leg quantities and
|
||||
price multipliers of 1,1,1 and -1 respectively. Leg SIN9 is the only quoting leg.
|
||||
Disable all Quote, Pre-Hedge and Post-Hedge paramaters (leg contracts are arbitrary, so if you
|
||||
dont have those contracts, choose any set of 4 contracts).
|
||||
|
||||
2. Enter a synthetic buy order of 10 lots at 40.00. The ladders will most likely be empty, so
|
||||
use the market view to enter orders.
|
||||
|
||||
3. You will see no quoting orders since all the 3 hedging legs have no markets. Now enter
|
||||
a 10 lot sell orders at 19.950 for SIH9, and 20.200 for SIK9. There should still be no quoting
|
||||
order for SIN9 since the SIF9 hedge leg still doesnt have a market. Enter a 1 lot sell order at
|
||||
20.00 for SIF9. This will result in the spreader sending a 1 lot quoting order at 20.150.
|
||||
|
||||
4. Now cancel the SIK9 order. This should result in the quoting order getting cancelled as well.
|
||||
Re-enter the SIK9 order, 10 lots at 20.200 and the quoting order should appear as well.
|
||||
|
||||
5. Now lift the quoting leg ask with a 1 lot buy order at the same price. Make sure hedges are sent (and filled)
|
||||
for all the 3 hedging legs. SIH9 should have no market now and hence we should have no quoting order.
|
||||
|
||||
6. Enter a 10 lot sell order at 20.000 for SIF9. This should result in a 9 lot quoting order.
|
||||
|
||||
7. Now randomly move the hedge orders as well as the spreader order a few times, and make sure the
|
||||
quoting order follows it and is correct
|
||||
quoting_order_price = (SIF9's ask + SIH9's ask + SIK9's ask - spreader_price)
|
||||
rounded up to the tick.
|
||||
|
||||
8. Now lift the complete quoter offer by crossing it with a 9 lot bid. Make sure all the hedges
|
||||
are sent and the spreader order reached a Completed state in the orderbook.
|
||||
|
||||
Reference in new issue
Block a user