Compare commits

...
26 changed files with 645 additions and 142 deletions

No files matched your search

+1
View File
@@ -39,6 +39,7 @@ SPARKD__CONFIG_DIR=src/server/sparkd/configurations/dev
# Defaults to a volume named "sparkd_<config_port>" (e.g., sparkd_8000).
# SPARKD__DATA_DIR=/home/bts/sparkdata/data
SPARKD__SOURCE_DIR=install/sparkd-release-gcc
SPARKD__RUNTIME_CONF_DIR=/home/ubuntu/8000/sparkdata/conf/
SPARKD__CONFIG_PORT=8000
SPARKD__QUICKBUS_PORT=8001
SPARKD__SIM_FIX_PORT=8002
+1 -1
View File
@@ -326,4 +326,4 @@
"description": "Test to run",
},
]
}
}
+37
View File
@@ -33,6 +33,43 @@ services:
resources:
limits:
memory: "${SPARKD__MEMORY:-4gb}"
sparkd-symbols:
volumes:
- ${SPARKD__DATA_DIR:-sparkd_data}:/home/bts/sparkdata/data
- ${SPARKD__RUNTIME_CONF_DIR:-sparkd_conf}:/home/bts/sparkdata/conf
build:
dockerfile: src/server/sparkd/Dockerfile
args:
- IMAGE=${SPARKD__IMAGE}
- SOURCE_DIR=${SPARKD__SOURCE_DIR}
- CONFIG_DIR=${SPARKD__CONFIG_DIR}
shm_size: "${SPARKD__SHM_SIZE:-512mb}"
command: >
sh -c "
echo bts | sudo -S chown -R bts:bts /home/bts/sparkdata/data
if [ -e "/home/bts/sparkdata/data/logging.sparkd/log" ]; then
mv "/home/bts/sparkdata/data/logging.sparkd/log" "/home/bts/sparkdata/data/logging.sparkd/log_$(date '+%Y%m%d-%H%M')"
fi
ls -alh /home/bts/sparkdata/conf/
cat /home/bts/sparkdata/conf/.env.local
if [ -e "/home/bts/sparkdata/conf/.env.local" ]; then
echo 'Sourcing /home/bts/sparkdata/conf/.env.local'
set -a
. /home/bts/sparkdata/conf/.env.local
set +a
fi
./sparkd run"
env_file:
- .env
- ${SPARKD__CONFIG_DIR}/.env
ports:
- "${SPARKD__CONFIG_PORT}:${SPARKD__CONFIG_PORT}"
- "${SPARKD__QUICKBUS_PORT}:${SPARKD__QUICKBUS_PORT}"
- "${SPARKD__SIM_FIX_PORT}:${SPARKD__SIM_FIX_PORT}"
deploy:
resources:
limits:
memory: "${SPARKD__MEMORY:-4gb}"
sparkd-tail:
image: bluetradingsystems/linux_dotnet7
@@ -55,24 +55,24 @@ OkxExecutionGateway::~OkxExecutionGateway() {}
enum class OkxExecutionState { OFF, STARTED, LOGGED_IN, SUBSCIRBED };
void OkxExecutionGateway::send_msgs(
std::vector<OrderMsg> const &msgs, trace::CorID) {
LOG(OrderMan,Info,"ExecutionGateway::send_msgs" );
LOG(OrderMan, Info, "ExecutionGateway::send_msgs");
for (const auto &msg : msgs) {
std::visit(
[this](auto &&order) {
using T = std::decay_t<decltype(order)>;
if constexpr (std::is_same_v<T, CreateOrderMsg>) {
LOG(OrderMan,Warn,"ExecutionGateway::CreateOrderMsg" );
LOG(OrderMan, Warn, "ExecutionGateway::CreateOrderMsg");
launch_new_order(order);
} else if constexpr (std::is_same_v<T, CancelOrderMsg>) {
LOG(OrderMan,Warn,"ExecutionGateway::CancelOrderMsg" );
LOG(OrderMan, Warn, "ExecutionGateway::CancelOrderMsg");
cancel_order(order);
} else if constexpr (std::is_same_v<T, CancelReplaceOrderMsg>) {
LOG(OrderMan,Warn,"ExecutionGateway::CancelReplaceOrderMsg" );
LOG(OrderMan, Warn, "ExecutionGateway::CancelReplaceOrderMsg");
modify_order(order);
} else if constexpr (std::is_same_v<T, RFQMsg>) {
// Handle RFQMsg if necessary
// adapter->handle_rfq(order); // Example placeholder
// Handle RFQMsg if necessary
// adapter->handle_rfq(order); // Example placeholder
}
},
msg);
@@ -94,7 +94,8 @@ struct OkxExecutionGateway::PImpl {
const std::string passphrase = config::get<config::OkxExeCfg>().passphrase();
const std::string url =
config::get<config::OkxExeCfg>().ws_url() + "/ws/v5/private";
const bool simulated_trading = config::get<config::OkxExeCfg>().simulated_trading();
const bool simulated_trading =
config::get<config::OkxExeCfg>().simulated_trading();
const std::string tdmode = config::get<config::OkxExeCfg>().tdmode();
const core::TimeSpan connect_timeout = core::TimeSpan::from_seconds(0);
const core::TimeSpan pong_timeout = core::TimeSpan::from_seconds(
@@ -124,7 +125,8 @@ struct OkxExecutionGateway::PImpl {
}
}
static void dummy(std::shared_ptr<const ExecutionReport>, const trace::CorID &) {
static void dummy(
std::shared_ptr<const ExecutionReport>, const trace::CorID &) {
LOG(OrderMan, Error, "OnixFixListener::Dummy fired");
}
void setCallback(const OnExeReport &callback) {
@@ -185,7 +187,7 @@ struct OkxExecutionGateway::PImpl {
Price(std::stod(order.at("lastPx").get<std::string>()));
report.avg_price = Price(std::stod(order.at("avgPx").get<std::string>()));
report.last_shares = Quantity::create_new(
std::stod(order.at("fillSz").get<std::string>()));
std::stod(order.at("fillSz").get<std::string>()));
//there are algos, like spreader
// that are super sensitive to messages and cancel themselves. To avoid
@@ -195,29 +197,28 @@ struct OkxExecutionGateway::PImpl {
// Alternatively we can change the spreader to not do that. Right not this
// seems to be the better choice
if (!(order.at("code").get<std::string>() == "0")) {
report.msg = order.at("msg").get<std::string>();
}
report.msg = order.at("msg").get<std::string>();
}
//report can be new order, cancel order, order filled, replace order, and partial fill or errror
//new order, cancelled and order filled dont need special handling, error gets handled above
//for other three we will look for fields that are only specific to the respectives types
// if (!(order.at("cancelSource").get<std::string>() == "")) {
//report can be new order, cancel order, order filled, replace order, and partial fill or errror
//new order, cancelled and order filled dont need special handling, error gets handled above
//for other three we will look for fields that are only specific to the respectives types
// if (!(order.at("cancelSource").get<std::string>() == "")) {
// }
// }
if (!(order.at("amendSource").get<std::string>() == "")) {
if((order.at("amendResult").get<std::string>() == "0"))
if ((order.at("amendResult").get<std::string>() == "0"))
report.exec_type = api::orderman::ExecType::EXEC_TYPE_REPLACED;
else{
else {
LOG(OrderMan, Warn, "order:{} replace rejected", report.order_key);
report.msg = order.at("msg").get<std::string>();
}
}
} else if (!(order.at("fillSz").get<std::string>() == "0")) {
utils::Uuid guid;
std::string fill_id = guid.to_text_string();
to_array(fill_id, &report.exchange_fill_id.value());
utils::Uuid guid;
std::string fill_id = guid.to_text_string();
to_array(fill_id, &report.exchange_fill_id.value());
}
// report.transact_time = ExchangeTimeStampVar(std::stoll(order.at("uTime").get<std::string>()));
ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
report.transact_time = now;
@@ -228,14 +229,14 @@ struct OkxExecutionGateway::PImpl {
bts_assert_msgf(
false, "Websocket stream client disconnected. {}", ec.message());
}
// fills execution report form basic_json and writes it to
// buffered_exec_reps, which is picked up from the other thread
// and passed over to the order infrastructure
void _push_execution_report(const nlohmann::basic_json<> &order) {
ExecutionReport er;
get_exe_report(order, er);
buffered_exec_reps.push(std::make_shared<ExecutionReport>(std::move(er)));
}
// fills execution report form basic_json and writes it to
// buffered_exec_reps, which is picked up from the other thread
// and passed over to the order infrastructure
void _push_execution_report(const nlohmann::basic_json<> &order) {
ExecutionReport er;
get_exe_report(order, er);
buffered_exec_reps.push(std::make_shared<ExecutionReport>(std::move(er)));
}
void on_message(const std::string &msg_str) {
LOG(OrderMan, Info, "okxlistener::on_message. {}", msg_str);
nlohmann::json msg;
@@ -279,21 +280,22 @@ struct OkxExecutionGateway::PImpl {
}
const auto &data = msg.at("data");
for (const nlohmann::basic_json<> &order : data) {
int orderkey = 0;
try {
int orderkey = 0;
try {
int temp = std::stoi(order.at("clOrdId").get<std::string>());
orderkey = temp;
}catch (...) {
}
if(orderkey ==0){
LOG(OrderMan, Error, "okxlistener::on_message GOT ER FOR ORDER NOT ENTERED FROM SYSTEM. {}", msg_str);
} catch (...) {
}
if (orderkey == 0) {
LOG(OrderMan, Error,
"okxlistener::on_message GOT ER FOR ORDER NOT ENTERED FROM "
"SYSTEM. {}",
msg_str);
continue;
}
_push_execution_report(order);
}
_push_execution_report(order);
}
}
void start() {
@@ -456,8 +458,8 @@ struct OkxExecutionGateway::PImpl {
};
void OkxExecutionGateway::start() { me_->start(); }
OnGwStatusChange &OkxExecutionGateway::on_gw_status_change() {
return me_->on_gw_status_change;
}
return me_->on_gw_status_change;
}
void OkxExecutionGateway::modify_order(
const CancelReplaceOrderMsg &msg) { //confirm will have everything
try {
@@ -474,7 +476,7 @@ void OkxExecutionGateway::modify_order(
std::to_string(msg.create_fields.quantity.full_value()));
auto headers = OkxUtils::createHeaders(
me_->api_key, me_->passphrase, me_->secret_key, "POST", REQUEST_PATH,
me_->simulated_trading,body);
me_->simulated_trading, body);
// Generate and log the curl command
std::string curlCommand = OkxUtils::generateCurlCommand(
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, headers,
@@ -488,7 +490,8 @@ void OkxExecutionGateway::modify_order(
if (suc) {
LOG(md, Info, "Http get succeeded {}", res.body);
} else {
LOG(md, Info, "Http get failed code: {}, errors: {}, body: {}", res.code, res.errors, res.body);
LOG(md, Info, "Http get failed code: {}, errors: {}, body: {}",
res.code, res.errors, res.body);
}
} catch (const std::exception &ex) {
const std::string err = ex.what();
@@ -496,17 +499,17 @@ void OkxExecutionGateway::modify_order(
}
}
void OkxExecutionGateway::dispatch_incoming_fix_messages() {
if (me_->buffered_exec_reps.empty()) {
return;
}
std::shared_ptr<ExecutionReport> er;
while (me_->buffered_exec_reps.try_pop(er)) {
trace::CorID cid = {};
LOG(OrderMan, Info,
"OkxExecutionGateway::dispatch_incoming_fix_messages er: {} ",
er->to_string());
me_->cb(std::move(er), cid);
}
if (me_->buffered_exec_reps.empty()) {
return;
}
std::shared_ptr<ExecutionReport> er;
while (me_->buffered_exec_reps.try_pop(er)) {
trace::CorID cid = {};
LOG(OrderMan, Info,
"OkxExecutionGateway::dispatch_incoming_fix_messages er: {} ",
er->to_string());
me_->cb(std::move(er), cid);
}
}
OnExeReport &OkxExecutionGateway::on_exe_report() { return me_->cb; }
OnCancelReject &OkxExecutionGateway::on_cancel_reject() {
@@ -538,12 +541,12 @@ void OkxExecutionGateway::launch_new_order(const CreateOrderMsg &msg) {
const std::string ord_type =
FIX::string_toLower(ord_full_name.substr(pos + suffix.size()));
const std::string body = fmt::format(
body_template, msg.security->okx().security_desc(),me_->tdmode, side, ord_type,
price, quantity, msg.key);
body_template, msg.security->okx().security_desc(), me_->tdmode, side,
ord_type, price, quantity, msg.key);
http::HttpResponse res;
auto headers = OkxUtils::createHeaders(
me_->api_key, me_->passphrase, me_->secret_key, "POST", REQUEST_PATH,
me_->simulated_trading,body);
me_->simulated_trading, body);
// Generate and log the curl command
std::string curlCommand = OkxUtils::generateCurlCommand(
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, headers,
@@ -556,30 +559,33 @@ void OkxExecutionGateway::launch_new_order(const CreateOrderMsg &msg) {
const auto &[suc, top_msg] = OkxUtils::orderAccepted(res.body);
if (suc) {
LOG(md, Info, "Http get succeeded {}", res.body);
// er.exec_type = api::orderman::ExecType::EXEC_TYPE_PENDING_NEW;
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_PENDING_NEW;
} else {
LOG(md, Info, "Http get failed code: {}, errors: {}, body: {}", res.code, res.errors, res.body);
OkxUtils::populateCreateOrderFields(er, msg);
er.price = msg.price;
er.quantity = msg.quantity;
er.filled_quantity = 0;
er.last_shares = 0;
er.leg_security = nullptr;
er.avg_price = Price(0);
ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now());
er.transact_time = now;
er.order_key = msg.key;
const auto &[id, ts, ex_msg] = OkxUtils::extractOrderInfo(res.body);
er.exchange_order_id = ExchangeOrderID{OkxUtils::stringToFixedArray(id)};
er.exec_type = api::orderman::ExecType::EXEC_TYPE_REJECTED;
er.order_status = api::orderman::NATIVE_ORDER_STATUS_REJECTED;
er.msg = ex_msg + " " + top_msg;
me_->buffered_exec_reps.push(
std::make_shared<ExecutionReport>(std::move(er)));
LOG(md, Info, "Http get succeeded {}", res.body);
// er.exec_type = api::orderman::ExecType::EXEC_TYPE_PENDING_NEW;
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_PENDING_NEW;
} else {
LOG(md, Info, "Http get failed code: {}, errors: {}, body: {}",
res.code, res.errors, res.body);
OkxUtils::populateCreateOrderFields(er, msg);
er.price = msg.price;
er.quantity = msg.quantity;
er.filled_quantity = 0;
er.last_shares = 0;
er.leg_security = nullptr;
er.avg_price = Price(0);
ExchangeTimeStamp64 now =
ExchangeTimeStamp64(datetime::unix_nano_now());
er.transact_time = now;
er.order_key = msg.key;
const auto &[id, ts, ex_msg] = OkxUtils::extractOrderInfo(res.body);
er.exchange_order_id =
ExchangeOrderID{OkxUtils::stringToFixedArray(id)};
er.exec_type = api::orderman::ExecType::EXEC_TYPE_REJECTED;
er.order_status = api::orderman::NATIVE_ORDER_STATUS_REJECTED;
er.msg = ex_msg + " " + top_msg;
me_->buffered_exec_reps.push(
std::make_shared<ExecutionReport>(std::move(er)));
}
}
}
// Shut down all reactors if they exist.
catch (const std::exception &ex) {
const std::string err = ex.what();
@@ -605,7 +611,7 @@ void OkxExecutionGateway::cancel_order(const CancelOrderMsg &msg) {
body_template, msg.security->okx().security_desc(), msg.key);
auto headers = OkxUtils::createHeaders(
me_->api_key, me_->passphrase, me_->secret_key, "POST", REQUEST_PATH,
me_->simulated_trading,body);
me_->simulated_trading, body);
// Generate and log the curl command
std::string curlCommand = OkxUtils::generateCurlCommand(
config::get<config::OkxExeCfg>().rest_url() + REQUEST_PATH, headers,
@@ -630,7 +636,8 @@ void OkxExecutionGateway::cancel_order(const CancelOrderMsg &msg) {
// er.exec_type = api::orderman::ExecType::EXEC_TYPE_PENDING_CANCEL;
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_PENDING_CANCEL;
} else {
LOG(md, Info, "Http get failed code: {}, errors: {}, body: {}", res.code, res.errors, res.body);
LOG(md, Info, "Http get failed code: {}, errors: {}, body: {}",
res.code, res.errors, res.body);
// er.exec_type = api::orderman::ExecType::EXEC_TYPE_REJECTED;
// er.order_status = api::orderman::NATIVE_ORDER_STATUS_REJECTED;
// er.msg = ex_msg + " " + top_msg;
@@ -643,8 +650,7 @@ void OkxExecutionGateway::cancel_order(const CancelOrderMsg &msg) {
OkxUtils::handleOkException("Cacnel order", err);
}
}
} // namespace bts::orderman::okxexe
} // namespace bts::orderman
// ExecutionReport OkxExecutionAdapter::fire_dummy_modify_order_report(CancelReplaceOrderMsg msg) {
// ExecutionReport *er_p = me_->create_exe_ptr();
@@ -156,23 +156,15 @@ void SpreaderOrder::_on_time(const bts::mercury::TimerEvent &t_event) {
}
}
void SpreaderOrder::_on_book_update(const BookUpdateEvent &book_event) {
if (book_event.key() == _bookkeeping_data().spread_sec_key()) {
// TODO, update spread book
_bookkeeping_data().spread_book = book_event.book;
LOG(OrderMan, Info,
"SpreaderOrder::_on_book_update spread book udpate, skey: {} "
"bid: {}/{} a: {}/{}",
book_event.key(), book_event.book.bid().at(0).price(),
book_event.book.bid().at(0).size(),
book_event.book.ask().at(0).price(),
book_event.book.ask().at(0).size());
return;
}
LOG(OrderMan, Diag,
"SpreaderOrder::_on_book_update skey: {} bid: {}/{} a: {}/{}",
book_event.key(), book_event.book.bid().at(0).price(),
book_event.book.bid().at(0).size(), book_event.book.ask().at(0).price(),
book_event.book.ask().at(0).size());
if (book_event.key() == _bookkeeping_data().spread_sec_key()) {
_bookkeeping_data().spread_book = book_event.book;
return;
}
if (!_active)
return;
auto scoped = _order_services->log_scope(
@@ -108,7 +108,7 @@ class SpreaderOrder : public Order, public ISpreaderManager {
public:
// inherited function to return the OrderDefinition. asserts
// if on_init_new has not been called yet
const api::orderman::OrderDefinition &definition() const;
const api::orderman::OrderDefinition &definition() const override;
// this is the construction, can be slow
void on_init_new(
const bts::api::orderman::OrderDefinition &order_def,
@@ -137,7 +137,11 @@ class SpreaderOrder : public Order, public ISpreaderManager {
bts::orderman::OrderKey spreader_order_key() const override { return key(); }
bts::TimeStamp current_time() const override { return _curr_timestamp; }
const bts::orderman::Book &get_book(size_t leg_index) const override {
return _bookkeeping_data().get_leg_data(leg_index).book();
const SpreaderLegData &leg_data =
_bookkeeping_data().get_leg_data(leg_index);
const auto &rv = leg_data.book();
return rv;
//return _bookkeeping_data().get_leg_data(leg_index).book();
}
const bts::orderman::Book &get_spread_book() const override {
@@ -1,5 +1,6 @@
#pragma once
#include "SpreaderFractionalQtyHelper.h"
#include "lib/api/services/OrderService.pb.h"
#include "lib/domain_model/SecurityKey.h"
#include "lib/domain_model/common.h"
#include "lib/orderman/ChildOrder.h"
@@ -20,6 +21,10 @@ class IAlternateOrderGateway;
// ii. create new quoter order
class ISpreaderManager {
public:
// definition() was added when we expanded the spreader to add reverse hedging and sniper.
// that needed spreader sid etc, which is all in the definition. Also spreader_order_key() can
// retired
virtual const api::orderman::OrderDefinition &definition() const = 0;
virtual bts::orderman::OrderKey spreader_order_key() const = 0;
virtual bts::TimeStamp current_time() const = 0;
// this is a reference to the original book structure
@@ -1,6 +1,8 @@
#include "LegPropertyData.h"
#include "ISpreaderManager.h"
#include "SpreaderUtils.h"
#include "lib/sparkproto/SparkProtos.pb.h"
#include "lib/utils/bts_assert.h"
namespace bts {
namespace orderman {
@@ -10,11 +12,35 @@ LegPropertyData::LegPropertyData(
const bts::api::ContractLeg &leg_contract,
const bts::spark::LegSpreaderParams &leg_params,
api::Side trading_side,
bts::spark::ExecutionStrategy::SpreaderType spreader_quoting_type,
bts::api::SyntheticContract::PriceCalc price_calc,
double qty_tick_size)
: _leg_index(leg_index), _leg_contract(leg_contract),
_leg_params(leg_params), _trading_side(trading_side),
_price_calc(price_calc), _qty_tick_size(qty_tick_size) {
switch (spreader_quoting_type) {
case bts::spark::ExecutionStrategy::SpreaderType::
ExecutionStrategy_SpreaderType_REQUOTE: {
_is_active_quoting = leg_params.has_quote_params();
break;
}
case bts::spark::ExecutionStrategy::SpreaderType::
ExecutionStrategy_SpreaderType_REVERSE_HEDGE: {
_is_active_quoting = leg_params.has_reverse_hedge_quote_params();
break;
}
case bts::spark::ExecutionStrategy::SpreaderType::
ExecutionStrategy_SpreaderType_SNIPER: {
_is_active_quoting = leg_params.has_sniper_quote_params();
break;
}
default: {
bts_assert(false);
break;
}
}
LOG(OrderMan, Info,
"LegPropertyData.cons leg_idx: "
"{} sec_key: {} side: {}",
@@ -19,6 +19,7 @@ class LegPropertyData {
const api::Side _trading_side;
const bts::api::SyntheticContract::PriceCalc _price_calc;
const double _qty_tick_size = 1.0;
bool _is_active_quoting;
public:
LegPropertyData(
@@ -26,6 +27,9 @@ class LegPropertyData {
const bts::api::ContractLeg &leg_contract,
const bts::spark::LegSpreaderParams &leg_params,
api::Side trading_side,
bts::spark::ExecutionStrategy::SpreaderType spreader_quoting_type =
bts::spark::ExecutionStrategy::SpreaderType::
ExecutionStrategy_SpreaderType_REQUOTE,
bts::api::SyntheticContract::PriceCalc price_calc =
bts::api::SyntheticContract::PRICECALC_STANDARD,
double qty_tick_size = 1.0);
@@ -52,11 +56,7 @@ class LegPropertyData {
double qty_tick_size() const { return _qty_tick_size; }
// TODO: is_quoting
//bool is_active_quoting() const { return _leg_params.is_active_quoting(); }
bool is_active_quoting() const {
return _leg_params.has_quote_params() ||
_leg_params.has_reverse_hedge_quote_params() ||
_leg_params.has_sniper_quote_params();
}
bool is_active_quoting() const { return _is_active_quoting; }
bool is_net_change() const {
return _price_calc == bts::api::SyntheticContract::PRICECALC_NET_CHANGE;
}
@@ -273,7 +273,9 @@ void SingleLegQuoter::_transform_to_WAITING_FOR_IOC() {
_switch_state(&_waiting_for_ioc_worker);
}
void SingleLegQuoter::_transform_to_WAITING_FOR_FIRST_ACK_CANCELLED() {
bts_assert(_worker->state() == QuoterState::WAITING_FOR_FIRST_ACK);
bts_assert(
_worker->state() == QuoterState::WAITING_FOR_FIRST_ACK ||
_worker->state() == QuoterState::WAITING_FOR_IOC);
_switch_state(&_waiting_for_first_ack_cancelled_state_worker);
}
void SingleLegQuoter::_transform_to_WORKING() {
@@ -4,6 +4,7 @@
#include "lib/orderman/orders/spreader_orders/spreader/LegPropertyData.h"
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderFractionalQtyHelper.h"
#include "lib/os/os.h"
#include "lib/utils/PBUtils.h"
namespace bts {
namespace orderman {
@@ -41,6 +42,11 @@ SpreaderBookkeepingData::SpreaderBookkeepingData(
_leg_data.emplace_back(
spreader_pao_services, spreader_order_key, _n_legs, spreader_manager,
quoter_ord_defs[i], hedger_ord_defs[i], leg_property_data);
LOG(OrderMan, Info,
"SpreaderBookkeepingData::cons leg_idx: {} quoter_ord_def: {} "
"hedger_ord_defs: {}",
i, utils::pb::to_string(quoter_ord_defs[i]),
utils::pb::to_string(hedger_ord_defs[i]));
bts_assert(
_sec_key_to_index.find(leg_property_data.security_key()) ==
_sec_key_to_index.end());
@@ -96,7 +96,10 @@ class SpreaderLegData {
bts_assert(_is_quoting);
return *_quoter;
}
const bts::orderman::Book &book() const { return _book; }
const bts::orderman::Book &book() const {
// book
return _book;
}
Price settlement_price() const { return Price(_settlement_price.price()); }
bool settlement_populated() const {
return !os::is_nan(_settlement_price.price());
@@ -162,6 +165,11 @@ class SpreaderLegData {
book, _settlement_price, _leg_property_data.tick_size(),
_leg_property_data.md_type(),
bts::api::SyntheticContract::PRICECALC_STANDARD, _book);
// LOG(OrderMan, Info,
// "SpreaderLegData::update_book index: {} lpd_key: {} book: {} book "
// "key: {}",
// _leg_property_data.leg_index(), _leg_property_data.security_key(),
// book, book.security_key());
}
void update_settlement_price(md::IntPrice price) {
_settlement_price = price;
@@ -1,5 +1,6 @@
#include "SpreaderUtils.h"
#include "lib/domain_model/common.h"
#include "lib/logging/logging.h"
#include "lib/md/PriceSize.h"
#include "lib/orderman/events/BookUpdateEvent.h"
#include "lib/orderman/orderman_utils/Book.h"
@@ -7,6 +8,7 @@
#include "lib/orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderBuilder.h"
#include "lib/orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderPreActivationOrderServices.h"
#include "lib/sparkproto/SparkProtos.pb.h"
#include "lib/utils/PBUtils.h"
#include "lib/utils/bts_assert.h"
#include <sstream>
namespace bts {
@@ -67,6 +69,8 @@ bool SpreaderUtils::resolve_synthetic_spread(
std::vector<bts::api::orderman::OrderDefinition> &out_quoter_defs,
std::vector<bts::api::orderman::OrderDefinition> &out_hedger_defs,
bool resolve_synthetic) {
LOG(OrderMan, Info, "g/ {}",
utils::pb::to_string(order_def));
auto get_quote_time_enforce =
[&](const spark::ExecutionStrategy &exec_strat,
const spark::LegSpreaderParams &q_leg_params) {
@@ -150,31 +154,32 @@ bool SpreaderUtils::resolve_synthetic_spread(
->mutable_limit()
->MergeFrom(api::orderman::LimitOrderDefinition());
}
// if (leg_params.quote_params().has_quote_hidden())
// quoter_order_def.set_time_in_force(
// bts::api::orderman::TimeInForce::TIME_IN_FORCE_IOC);
// else
// quoter_order_def.set_time_in_force(
// order_def.time_in_force());
auto tif =
get_quote_time_enforce(execution_strategy, leg_params);
quoter_order_def.set_time_in_force(tif);
// hedger order def
api::orderman::OrderDefinition hedger_order_def =
quoter_order_def;
hedger_order_def.mutable_custom_def()
->mutable_limit()
->MergeFrom(api::orderman::LimitOrderDefinition());
quoter_order_def.set_time_in_force(order_def.time_in_force());
//quoter_order_def.set_time_in_force(order_def.time_in_force());
hedger_order_def.set_sub_type(
bts::api::orderman::OrderSubType::
ORDER_SUB_TYPE_HEDGING_LEG);
LegPropertyData leg_property_data(
leg_idx, leg, leg_params, leg_order_side,
order_def.custom_def()
.spreader()
.execution_strategy()
.spreader_type(),
sec.synthetic().price_calc());
out_resolved.push_back(leg_property_data);
LOG(OrderMan, Info,
"SpreaderUtils::resolve_synthetic_spread idx: {} "
"quoter_order_def: {}",
out_quoter_defs.size(),
utils::pb::to_string(quoter_order_def));
out_quoter_defs.push_back(quoter_order_def);
out_hedger_defs.push_back(hedger_order_def);
leg_idx++;
@@ -5,6 +5,7 @@
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/common.h"
#include "lib/jetfix/codec/FieldDefinitions.h"
#include "lib/logging/logging.h"
#include "lib/md/IntPrice.h"
#include "lib/md/PriceSize.h"
#include "lib/md/Shm/MDStructure.h"
@@ -469,6 +470,12 @@ class SpreaderUtils {
api::Side order_side,
double tick_size,
const Book &book) {
LOG(OrderMan, Info,
"SpreaderUtils::book_through_order_price_nticks: order_price: {} "
"order_side: {} tick_size: {} -- bid: {}/{} ask: {}/{}",
order_price, static_cast<int>(order_side), tick_size,
book.bid().at(0).price(), book.bid().at(0).size(),
book.ask().at(0).price(), book.ask().at(0).size());
if (order_side == api::SIDE_BUY) {
const md::PriceSize &top_ask = book.ask()[0];
if (top_ask.size() == 0)
@@ -487,20 +494,35 @@ class SpreaderUtils {
}
static Price compute_order_price_through_book(
const Book &book, api::Side order_side, int n_ticks) {
const Book &book, api::Side order_side, double tick_size, int n_ticks) {
LOG(OrderMan, Info,
"SpreaderUtils::compute_order_price_through_book tick_size: {} "
"n_ticks: {}",
tick_size, n_ticks);
switch (order_side) {
case api::Side::SIDE_BUY: {
if (n_ticks >= 0) {
return Price(book.ask()[n_ticks].price());
double top_ask = book.ask()[0].price();
return Price(top_ask + tick_size * n_ticks);
//return Price(book.ask()[n_ticks].price());
} else {
return Price(book.bid()[std::abs(n_ticks)].price());
double top_bid = book.bid()[0].price();
return Price(top_bid - tick_size * n_ticks);
//return Price(book.bid()[std::abs(n_ticks)].price());
}
}
case api::Side::SIDE_SELL: {
if (n_ticks >= 0) {
return Price(book.bid()[n_ticks].price());
double top_bid = book.bid()[0].price();
double rv = top_bid - tick_size * n_ticks;
LOG(OrderMan, Info,
"SpreaderUtils::compute_order_price_through_book top_bid: {} "
"rv: {}",
top_bid, rv);
return Price(rv);
} else {
return Price(book.ask()[std::abs(n_ticks)].price());
double top_ask = book.ask()[0].price();
return Price(top_ask + tick_size * n_ticks);
}
}
default: {
@@ -1,7 +1,9 @@
#include "lib/orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForSizeStateWorker.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/common.h"
#include "lib/logging/logging.h"
#include "lib/md/PriceSize.h"
#include "lib/orderman/orderman_utils/Book.h"
#include "lib/orderman/orders/spreader_orders/spreader/ISpreaderManager.h"
#include "lib/orderman/orders/spreader_orders/spreader/LegPropertyData.h"
#include "lib/orderman/orders/spreader_orders/spreader/SingleLegQuoter.h"
@@ -63,13 +65,6 @@ void QuoterWaitingForSizeStateWorker::_longpath_analyze_and_act() {
_quoter->_transform_to_END();
return;
} else {
if (_quoter->_this_leg_data.leg_params()
.has_reverse_hedge_quote_params()) {
}
if (_quoter->_this_leg_data.leg_params().has_sniper_quote_params()) {
} else if (_quoter->_this_leg_data.leg_params().has_quote_params()) {
}
auto spreader_q_type = _quoter->_spreader.get_spreader_quoting_type();
switch (spreader_q_type) {
case bts::spark::ExecutionStrategy::SpreaderType::
@@ -81,7 +76,7 @@ void QuoterWaitingForSizeStateWorker::_longpath_analyze_and_act() {
// only applicable for > 2 legged spreads
_quoter->_commit_drivers(out_price);
LOG(OrderMan, Info,
"QuoterWaitingForSizeStateWorker sending order "
"QuoterWaitingForSizeStateWorker (requoting) sending order "
"key:{} price:{} quantity:{}",
_quoter->_key(), out_price, out_quantity);
// this is the only place where a quoter order is entered
@@ -92,18 +87,27 @@ void QuoterWaitingForSizeStateWorker::_longpath_analyze_and_act() {
}
case bts::spark::ExecutionStrategy::SpreaderType::
ExecutionStrategy_SpreaderType_REVERSE_HEDGE: {
auto is_book_populated = [&](const Book &leg_book, api::Side bs) {
const auto &book_side = leg_book.side(bs);
return !os::is_nan(book_side.at(0).price());
};
const LegPropertyData &leg_data = _quoter->_this_leg_data;
bts_assert(_quoter->_this_leg_data.leg_params()
.has_reverse_hedge_quote_params());
bts_assert(leg_data.leg_params().has_reverse_hedge_quote_params());
const auto &rev_hedge_qparams =
_quoter->_this_leg_data.leg_params().reverse_hedge_quote_params();
leg_data.leg_params().reverse_hedge_quote_params();
int n_ticks = rev_hedge_qparams.n_ticks();
const Book &spread_book = _quoter->_spreader.get_spread_book();
auto leg_qside = leg_data.trading_side();
auto spread_side = _quoter->_spreader.definition().side();
int book_through_ticks =
SpreaderUtils::book_through_order_price_nticks(
_quoter->_price_quantity_state.out_price(), leg_qside,
_quoter->_this_leg_data.tick_size(), spread_book);
_quoter->_price_quantity_state.target_spread_price(),
// TODO (get it from the spread synth sec)
spread_side, 0.1, spread_book);
LOG(OrderMan, Info,
"QuoterWaitingForSizeStateWorker::_longpath_analyze_and_act("
"reverse hedge) "
"n_ticks: {} book_through_ticks: {}",
n_ticks, book_through_ticks);
if (book_through_ticks >= n_ticks) {
// qty, shoot full size? (can min it by max available on hedge leg)
//auto spr_qty = _quoter->_price_quantity_state.out_quantity();
@@ -117,8 +121,25 @@ void QuoterWaitingForSizeStateWorker::_longpath_analyze_and_act() {
// price: (nticks into the opposite market)
const Book &leg_book =
_quoter->_spreader.get_book(leg_data.leg_index());
bool leg_book_populated = is_book_populated(
leg_book, SpreaderUtils::invert_side(leg_data.trading_side()));
if (!leg_book_populated) {
LOG(OrderMan, Info,
"QuoterWaitingForSizeStateWorker::_longpath_analyze_and_act "
"(RH), quoting leg book is empty, not firing order");
return;
}
LOG(OrderMan, Info,
"QuoterWaitingForSizeStateWorker::_longpath_analyze_and_act "
"leg_book: {} k: {}",
leg_book, leg_book.security_key());
Price order_price = SpreaderUtils::compute_order_price_through_book(
leg_book, leg_data.trading_side(), n_ticks);
leg_book, leg_data.trading_side(), leg_data.tick_size(),
n_ticks);
LOG(OrderMan, Info,
"QuoterWaitingForSizeStateWorker sending order in reverse "
"hedging mode key: {} price: {} quantity: {}",
_quoter->_key(), order_price, order_qty);
_quoter->_place_order(order_price, order_qty);
_quoter->_transform_to_WAITING_FOR_IOC();
}
@@ -139,6 +139,9 @@ class SpreaderManagerForTest : public ISpreaderManager {
return _bookkeeping_data.spread_book.value();
//return get_book(0);
}
const api::orderman::OrderDefinition &definition() const override {
return _bookkeeping_data.order_def();
}
// the caller can keep a reference to the returned type forever. the
// data in the returned type is all const, so nothing will change
// beneath it
+1 -1
View File
@@ -40,7 +40,7 @@ message LegSpreaderParams
}
/// Legs can be quoted actively, or sent in reponse
/// to fills
bool is_active_quoting = 20;
bool is_active_quoting = 20;
bool enable_hedging = 30;
QuoteParams quote_params = 40;
ReverseHedgeQuoteParams reverse_hedge_quote_params = 43;
@@ -1,3 +1,4 @@
SPARKD__CONFIG_DIR=src/server/sparkd/configurations/alphaworx_prod
SPARKD__SHM_SIZE=8gb
SPARKD__MEMORY=8gb
SPARKD__RUNTIME_CONF_DIR=/home/ubuntu/8000/sparkdata/conf/
@@ -0,0 +1,4 @@
SPARKD__CONFIG_DIR=src/server/sparkd/configurations/alphaworx_zen_hk_live
SPARKD__SHM_SIZE=8gb
SPARKD__MEMORY=8gb
SPARKD__RUNTIME_CONF_DIR=/home/bts/8000/sparkdata/conf/
@@ -0,0 +1,6 @@
* Connects to Binance and Okx PRODUCTION sites for market data and execution
* Api keys NOT included
* Configured for Alphaworx
To retrieve api keys, run "python init_env.py". It should dump them to a file
called ".env.local".
@@ -0,0 +1,9 @@
{
"api_key": "${BINANCE_API_KEY}",
"secret_key": "${BINANCE_SECRET_KEY}",
"account": "PROD",
"rest_endpoint": "https://api.binance.com",
"ws_endpoint": "wss://ws-api.binance.com:443/ws-api/v3",
"ws_stream_endpoint": "wss://stream.binance.com:9443",
"use_cross_margin_api": true
}
@@ -0,0 +1,9 @@
{
"api_key": "${BINANCE_API_KEY}",
"secret_key": "${BINANCE_SECRET_KEY}",
"account": "PROD",
"rest_endpoint": "https://fapi.binance.com",
"ws_endpoint": "wss://ws-fapi.binance.com/ws-fapi/v1",
"ws_stream_endpoint": "wss://fstream.binance.com",
"use_cross_margin_api": false
}
@@ -0,0 +1,88 @@
import botocore
import json
import os
from boto3.session import Session
from botocore.exceptions import ClientError
from fileinput import FileInput
def update_env_file(path, env_vars: dict):
"""Update or add vars in a .env file in place."""
if not os.path.exists(path):
with open(path, "w") as file:
for k, v in env_vars.items():
file.write(f"{k}={v}\n")
return
with FileInput(path, inplace=True) as file:
for line in file:
if not line.startswith("#"):
stripped: str = line.lstrip()
key_len = stripped.find("=")
if key_len >= 0:
k = stripped[0:key_len]
if k in env_vars:
print(f"{k}={env_vars[k]}")
del env_vars[k]
continue
print(line, end="")
if len(env_vars) > 0:
with open(path, "a") as file:
for k, v in env_vars.items():
file.write(f"{k}={v}\n")
def get_secret():
secret_name = "alphaworx-binance-api-keys"
region_name = "ap-northeast-1"
profile_name = "AdministratorAccess-849458505639"
session = Session(profile_name=profile_name)
client = session.client(
service_name="secretsmanager",
region_name=region_name,
)
def on_token_error(e: Exception):
print(f"""
Failed to get secret value. {e}
Verify you are logged in to aws, then try again.
aws sso login --profile {profile_name}
If this error persists, verify you have access to these api keys.
For more help or information, talk to John.
""")
try:
get_secret_value_response = client.get_secret_value(
SecretId=secret_name)
except ClientError as e:
print(f"Failed to get secret value.\n{e}")
raise e
except botocore.exceptions.SSOTokenLoadError as tl_err:
on_token_error(tl_err)
return None
except botocore.exceptions.TokenRetrievalError as tr_err:
on_token_error(tr_err)
return None
return get_secret_value_response["SecretString"]
def _run():
this_dir = os.path.dirname(__file__)
secret_str = get_secret()
if secret_str is None:
exit(1)
secret = json.loads(secret_str)
env_vars = {}
if "api-key" in secret:
env_vars["BINANCE_API_KEY"] = secret["api-key"]
if "secret-key" in secret:
env_vars["BINANCE_SECRET_KEY"] = secret["secret-key"]
update_env_file(f"{this_dir}/.env.local", env_vars)
if __name__ == "__main__":
_run()
@@ -0,0 +1,124 @@
QuickbusCfg {
value {
node {
reconnect_period_secs: 1
hb_period_msecs: 15000
max_missed_hbs: 5
parent_host: ""
parent_port: 0
my_port: 0
}
client {
reconnect_period_secs: 1
hb_period_msecs: 15000
max_missed_hbs: 5
parent_host: @SystemConfig.config_ip
parent_port: @Ports.qbus_port
my_port: 0
}
}
}
JetFIX {
value {
connection {
name: "binance_1"
venue: VENUE_BINANCE
max_order_msgs_per_window: 300
window_duration_msecs: 1000
}
connection {
name: "binance_usds_futures_1"
venue: VENUE_BINANCE_USDS_FUTURES
max_order_msgs_per_window: 300
window_duration_msecs: 1000
}
connection {
name: "okx_1"
venue: VENUE_OKX
max_order_msgs_per_window: 300
window_duration_msecs: 1000
}
}
}
MarketDataConfig {
value {
binance {
num_depth_levels: 5
url_rest_base: "https://api.binance.com"
url_ws_base: "wss://stream.binance.com:9443"
security_criteria {
clauses {
security_types: SECURITY_TYPE_SPOT
symbols: @SymbolLists.list[Binance].symbol
name_regexps: "USDC|USDT$"
}
}
}
binance_usds_futures {
num_depth_levels: 5
url_rest_base: "https://fapi.binance.com"
url_ws_base: "wss://fstream.binance.com"
security_criteria {
clauses {
security_types: SECURITY_TYPE_FUTURE
symbols: @SymbolLists.list[BinanceUsdsFutures].symbol
}
}
}
okx {
num_depth_levels: 5
url_rest_base: "https://aws.okx.com"
url_ws_base: "wss://wsaws.okx.com:8443"
security_criteria {
clauses {
security_types: SECURITY_TYPE_SPOT
symbols: @SymbolLists.list[OkxSpots].symbol
}
clauses {
security_types: SECURITY_TYPE_FUTURE
symbols: @SymbolLists.list[OkxFutures].symbol
}
}
is_application_ping_pong_enabled: true
application_ping_interval_ms: 20000
application_pong_timeout_ms: 10000
is_protocol_ping_pong_enabled: true
protocol_ping_interval_ms: 60000
protocol_pong_timeout_ms: 30000
}
}
}
NotSimExecution {
value {
entries {
venue: VENUE_BINANCE
}
entries {
venue: VENUE_BINANCE_USDS_FUTURES
}
entries {
venue: VENUE_OKX
}
}
}
OkxExeCfg {
value {
account: "PROD"
ops_id: "op_id_bts"
secret_key: "240038D7A28D7F9461247CFCB8E43710"
api_key: "dc188532-c74c-4d40-affe-3c6bf4d0ca9a"
passphrase: ":,7i%tA%ivfj%SCbX?Ut"
ws_url: "wss://wsaws.okx.com:8443"
rest_url: "https://www.okx.com"
ws_ping_timeout_seconds: 10
ws_ping_interval_seconds: 25
ws_reconnect_interval_seconds: 5
ws_reconnect_max_count: 5
simulated_trading: false
tdmode: "cross"
}
}
@@ -0,0 +1,124 @@
SymbolLists {
value {
list {
key: "Binance"
symbol: "ADA"
symbol: "APT"
symbol: "AVAX"
symbol: "BCH"
symbol: "BNB"
symbol: "BTC"
symbol: "DOGE"
symbol: "DOT"
symbol: "ETH"
symbol: "HBAR"
symbol: "LINK"
symbol: "LTC"
symbol: "OM"
symbol: "PAXG"
symbol: "PEPE"
symbol: "S"
symbol: "SHIB"
symbol: "SOL"
symbol: "SUI"
symbol: "TAO"
symbol: "TRUMP"
symbol: "USDC"
symbol: "XRP"
}
list {
key: "BinanceUsdsFutures"
symbol: "1000PEPE"
symbol: "1000SHIB"
symbol: "ADA"
symbol: "APT"
symbol: "AVAX"
symbol: "BCH"
symbol: "BNB"
symbol: "BTC"
symbol: "DOGE"
symbol: "DOT"
symbol: "ETH"
symbol: "HBAR"
symbol: "LINK"
symbol: "LTC"
symbol: "OM"
symbol: "S"
symbol: "SOL"
symbol: "SUI"
symbol: "TAO"
symbol: "TRUMP"
symbol: "USDC"
symbol: "XRP"
}
list {
key: "OkxSpots"
symbol: "ADA"
symbol: "APT"
symbol: "AVAX"
symbol: "BCH"
symbol: "BETH"
symbol: "BNB"
symbol: "BTC"
symbol: "DOGE"
symbol: "DOT"
symbol: "ETH"
symbol: "HBAR"
symbol: "LINK"
symbol: "LTC"
symbol: "OM"
symbol: "PEPE"
symbol: "S"
symbol: "SHIB"
symbol: "SOL"
symbol: "SUI"
symbol: "TRUMP"
symbol: "USDC"
symbol: "USDT"
symbol: "XRP"
}
list {
key: "OkxFutures"
symbol: "ADA"
symbol: "APT"
symbol: "AVAX"
symbol: "BCH"
symbol: "BNB"
symbol: "BTC"
symbol: "DOGE"
symbol: "DOT"
symbol: "ETH"
symbol: "HBAR"
symbol: "LINK"
symbol: "LTC"
symbol: "OM"
symbol: "PEPE"
symbol: "S"
symbol: "SHIB"
symbol: "SOL"
symbol: "SUI"
symbol: "TAO"
symbol: "TRUMP"
symbol: "USDC"
symbol: "XRP"
}
}
}
Symbols {
value {
venues {
name: "VENUE_BINANCE"
spot: @SymbolLists.list[Binance].symbol
}
venues {
name: "VENUE_BINANCE_USDS_FUTURES"
future: @SymbolLists.list[BinanceUsdsFutures].symbol
}
venues {
name: "VENUE_OKX"
spot: @SymbolLists.list[OkxSpots].symbol
future: @SymbolLists.list[OkxFutures].symbol
}
}
}
+2 -2
View File
@@ -47,7 +47,7 @@ def _get_arg_parser():
)
# TODO: add more services (api-server, pubsub, dsqa web server, etc.)
all_services = ["sparkd"]
parser.add_argument("services", nargs="*")
parser.add_argument("services", help="this selects services defined in compose.yaml", nargs="*")
parser.set_defaults(
configuration="release",
services=all_services,
@@ -70,7 +70,7 @@ def _get_env_files(services, profile):
add(root.joinpath(".env"))
add(root.joinpath(".env.local"))
if profile is not None:
if "sparkd" in services:
if "sparkd" in services or "sparkd-symbols" in services:
profile_dir=root.joinpath("src/server/sparkd/configurations", profile)
add(profile_dir.joinpath(".env"))
add(profile_dir.joinpath(".env.local"))