Compare commits

...
4 Commits
5 changed files with 286 additions and 42 deletions

No files matched your search

@@ -40,10 +40,8 @@ namespace mbts.analytics {
/// last computed min/max driver for each exp
private IDictionary<VolSurfaceExpiration, Tuple<double, double>>
_computed_driver_range_map = new Dictionary<VolSurfaceExpiration, Tuple<double, double>>();
private readonly IEnumerable<double> _default_abs_moves =
new double[] { 300, -200, -125, -100, -75, -50, -25, 0, 25, 50, 75, 100, 125, 200, 300 };
private readonly IEnumerable<double> _default_abs_moves = config.Configuration.get<bts.config.VolSurface>().default_abs_moves;
private readonly double _cushion_percentage = 25.0;
public event
Action<IDictionary<VolSurfaceExpiration, IEnumerable<Theos>>, double, double> on_theos =
(o, vt, tt) => { };
@@ -52,7 +50,7 @@ namespace mbts.analytics {
(o, vt, tt) => { };
public VolSurfaceInterpGreeksProducer(int vs_id) {
Log.Gen.info($"VolSurfaceInterpGreeksProducer.cons vs_id: {vs_id}");
Log.Gen.info($"VolSurfaceInterpGreeksProducer.cons vs_id: {vs_id}, _default_abs_moves: {string.Join(", ", _default_abs_moves)}");
_vs_id = vs_id;
}
@@ -0,0 +1,22 @@
VolSurface {
value {
default_abs_moves: -600
default_abs_moves: -300
default_abs_moves: -200
default_abs_moves: -125
default_abs_moves: -100
default_abs_moves: -75
default_abs_moves: -50
default_abs_moves: -25
default_abs_moves: 0
default_abs_moves: 25
default_abs_moves: 50
default_abs_moves: 75
default_abs_moves: 100
default_abs_moves: 125
default_abs_moves: 200
default_abs_moves: 300
default_abs_moves: 600
}
}
@@ -0,0 +1,6 @@
syntax = 'proto2';
package bts.config;
message VolSurface {
repeated double default_abs_moves = 1;
}
@@ -300,6 +300,9 @@ extern "C" void init_execution_tester_plugin(bts::SparkServices &srv) {
extern "C" void init_task_eye_plugin(bts::SparkServices &srv) {
DynamicOrderDescriptor tasking_eye_algo;
tasking_eye_algo.set_dynamic_order_type("Tasking Eye");
// if this is set to true, it waits for the existance of Signal File
*tasking_eye_algo.add_param_desc() = param_bool("Wait For Signal");
*tasking_eye_algo.add_param_desc() = param_string("Signal File");
*tasking_eye_algo.add_param_desc() = param_string("Account");
*tasking_eye_algo.add_param_desc() = param_double("Edge");
*tasking_eye_algo.add_param_desc() = param_double("Shot Size");
@@ -23,10 +23,17 @@
#include "lib/utils/bts_assert.h"
#include <bits/stdint-uintn.h>
#include <boost/algorithm/string.hpp>
#include <chrono>
#include <elf.h>
#include <filesystem>
#include <google/protobuf/timestamp.pb.h>
#include <google/protobuf/util/time_util.h>
#include <iostream>
#include <optional>
#include <sstream>
#include <string>
#include <thread>
#include <tuple>
#include <unordered_map>
using namespace bts::api::orderman;
@@ -69,6 +76,11 @@ double eps = 0.0000001;
bool eq(double d1, double d2) { return std::abs(d1 - d2) < eps; }
bool greater_then_eq(double d1, double d2) { return eq(d1, d2) || d2 > d1; }
void sleep_log() {
return;
std::this_thread::sleep_for(std::chrono::seconds(5));
}
// Returns the number of shots that are within the Shots Window
size_t n_recent_shots(
const std::vector<TEAShot> &shots, uint64_t window_nanos) {
@@ -100,7 +112,91 @@ bool in_flight_orders(const std::vector<TEAShot> &shots) {
return false;
}
std::set<SecurityKey> parse_securities(const std::string &secs) {
// returns the securities that match the criteria from the 2nd - 8th arg
std::set<SecurityKey> match_securities(
const std::vector<api::Security const *> &secs,
api::Venue venue,
const std::string &symbol,
uint32_t exp_month,
uint32_t exp_day,
uint32_t exp_yr,
std::tuple<double, double> strike_range,
api::PutOrCall cp) {
std::set<SecurityKey> rv = {};
double strike_min = std::get<0>(strike_range);
double strike_max = std::get<1>(strike_range);
for (const auto &s : secs) {
if (s->exchange() == venue && s->has_option() && s->symbol() == symbol) {
const auto &oc = s->option();
if (oc.call_put() == cp && oc.strike() >= strike_min &&
oc.strike() <= strike_max && oc.expiration_year() == exp_yr &&
oc.expiration_month() == exp_month &&
oc.expiration_day() == exp_day)
rv.insert(SecurityKey(s->id()));
}
}
return rv;
}
// Parses the sec expressed as text in the secs string.
// the format is symbol:exp:call/put:strike range, ex
// BTC:08/14/2023:Call:25000-30000
std::set<SecurityKey> parse_securities_from_range(
const std::string &secs,
const std::vector<api::Security const *> &sec_vec) {
LOG(OrderMan, Info,
"TaskingEyeAlgo::parse_securities_from_range secs: {} sec_vec: {}", secs,
sec_vec.size());
sleep_log();
if (secs.empty())
return {};
auto parse_exp = [](const std::string &exp_str) {
LOG(OrderMan, Info,
"TaskingEyeAlgo::parse_securities_from_range parse_exp exp: {}",
exp_str);
std::vector<std::string> exp_parts;
boost::split(exp_parts, exp_str, boost::is_any_of("/"));
LOG(OrderMan, Info,
"TaskingEyeAlgo::parse_securities_from_range exp_parts: {}",
exp_parts.size());
uint32_t mm = static_cast<std::uint32_t>(std::stoul(exp_parts[0]));
uint32_t dd = static_cast<std::uint32_t>(std::stoul(exp_parts[1]));
uint32_t yy = static_cast<std::uint32_t>(std::stoul(exp_parts[2]));
LOG(OrderMan, Info,
"TaskingEyeAlgo::parse_securities_from_range {} mm: {} dd: {} yy: {}",
mm, dd, yy);
return std::tuple<uint32_t, uint32_t, uint32_t>(mm, dd, yy);
};
auto parse_strike_range = [](const std::string &strike_range_str) {
std::vector<std::string> strike_parts;
boost::split(strike_parts, strike_range_str, boost::is_any_of("-"));
double k_min = std::stod(strike_parts[0]);
double k_max = std::stod(strike_parts[1]);
return std::tuple<double, double>(k_min, k_max);
};
std::vector<std::string> parts;
boost::split(parts, secs, boost::is_any_of(":"));
const std::string &symbol = parts[0];
const std::string &exp_str = parts[1];
const std::string &cp_str = parts[2];
const auto &strike_range_str = parts[3];
LOG(OrderMan, Info,
"TaskingEyeAlgo::parse_securities_from_range symbol: {} exp: {} cp: {} "
"stike_range: {}",
symbol, exp_str, cp_str, strike_range_str);
// debugging
sleep_log();
auto [mm, dd, yy] = parse_exp(exp_str);
auto strike_range = parse_strike_range(strike_range_str);
api::PutOrCall cp = (cp_str[0] == 'C' || cp_str[0] == 'c')
? api::PutOrCall::PUTORCALL_CALL
: api::PutOrCall::PUTORCALL_PUT;
return match_securities(
sec_vec, api::Venue::VENUE_DERIBIT, symbol, mm, dd, yy, strike_range,
cp);
}
std::set<SecurityKey> parse_securities_from_keys(const std::string &secs) {
std::set<SecurityKey> rv = {};
std::vector<std::string> parts;
boost::split(parts, secs, boost::is_any_of(","));
@@ -113,10 +209,34 @@ std::set<SecurityKey> parse_securities(const std::string &secs) {
return rv;
}
std::set<SecurityKey> parse_securities(
const std::string &secs,
const std::vector<api::Security const *> &sec_vec) {
bts_assert(!secs.empty());
LOG(OrderMan, Info, "TaskingEyeAlgo::parse_securities sec: {}", secs);
sleep_log();
std::set<SecurityKey> rv = {};
if (secs.find("/") != std::string::npos &&
secs.find(":") != std::string::npos) {
rv = parse_securities_from_range(secs, sec_vec);
} else {
rv = parse_securities_from_keys(secs);
}
std::stringstream ss;
for (auto k : rv) {
ss << k << " ";
}
LOG(OrderMan, Info, "TaskingEyeAlgo::parse_securities sec: {} rv: {}", secs,
ss.str());
return rv;
}
struct TaskingEyeAlgo ::PImpl {
IAlgoServices *service_ptr = nullptr;
analytics::ITheoCalculator const *theo_calc = nullptr;
TEAState state = TEAState::ACTIVE;
bool wait_for_signal = false;
std::string signal_file = "";
double edge = 100000;
double shot_size = 0;
double target_qty = 0;
@@ -150,31 +270,62 @@ TaskingEyeAlgo::~TaskingEyeAlgo() {}
OrderOpResult TaskingEyeAlgo::do_before_launch(
IBeforeLaunchAlgoServices &serv) {
auto is_data_valid = [&]() {
return greater_then_eq(0.1, me->shot_size) &&
greater_then_eq(1, me->target_qty) &&
greater_then_eq(1, me->max_shots_per_window) &&
greater_then_eq(1000000000, me->window_size_nanos) &&
me->side != api::SIDE_UNDEFINED;
bool rv = greater_then_eq(0.1, me->shot_size) &&
greater_then_eq(1, me->target_qty) &&
greater_then_eq(1, me->max_shots_per_window) &&
greater_then_eq(1000000000, me->window_size_nanos) &&
me->side != api::SIDE_UNDEFINED;
bool signal_correct = !me->wait_for_signal || !me->signal_file.empty();
rv = rv && signal_correct;
return rv;
};
LOG(OrderMan, Info,
"TaskingEyeAlgo::do_before_launch order_key: {} security: {} "
"definition: {}",
serv.order_key().value(), utils::pb::to_string(serv.security()),
utils::pb::to_string(serv.definition()));
sleep_log();
auto &def = serv.definition();
if (def.has_custom_def() && def.custom_def().has_dynamic()) {
const auto &d_params = def.custom_def().dynamic().order_params();
auto wait_for_signal = get_if<bool>(d_params, "Wait For Signal");
me->wait_for_signal =
wait_for_signal.has_value() ? wait_for_signal.value() : false;
auto signal_file = get_if<std::string>(d_params, "Signal File");
me->signal_file = signal_file.has_value() ? signal_file.value() : "";
me->edge = get_if<double>(d_params, "Edge").value();
LOG(OrderMan, Info, "TaskingEyeAlgo::do_before_launch edge: {}",
me->edge);
sleep_log();
me->shot_size = get_if<double>(d_params, "Shot Size").value();
LOG(OrderMan, Info, "TaskingEyeAlgo::do_before_launch shot_size: {}",
me->shot_size);
sleep_log();
me->target_qty = get_if<double>(d_params, "Target Quantity").value();
LOG(OrderMan, Info, "TaskingEyeAlgo::do_before_launch target_qty: {}",
me->target_qty);
sleep_log();
// TODO, make sure the side if right
me->side = def.side();
me->max_shots_per_window =
get_if<uint32_t>(d_params, "Max Shots Per Window").value();
LOG(OrderMan, Info,
"TaskingEyeAlgo::do_before_launch max_shots_per_window: {}",
me->max_shots_per_window);
sleep_log();
uint32_t window_size_secs =
get_if<uint32_t>(d_params, "Window Size (secs)").value();
LOG(OrderMan, Info,
"TaskingEyeAlgo::do_before_launch window_size_secs: {}",
window_size_secs);
sleep_log();
me->window_size_nanos = seconds_to_nanos(window_size_secs);
auto sec_str = get_if<std::string>(d_params, "Securities").value();
auto parsed_secs = parse_securities(sec_str);
LOG(OrderMan, Info, "TaskingEyeAlgo::do_before_launch Securities: {}",
sec_str);
sleep_log();
auto parsed_secs = parse_securities(sec_str, serv.known_securities());
std::stringstream ss;
std::vector<SecurityKey> final_secs;
if (parsed_secs.empty()) {
final_secs.push_back(SecurityKey{serv.security().id()});
@@ -186,18 +337,21 @@ OrderOpResult TaskingEyeAlgo::do_before_launch(
auto ct = get_tea_child_template(serv, sec_key, me->side);
me->sec_map.insert({sec_key, {ct}});
LOG(OrderMan, Info,
"TaskingEyeAlgo::do_before_launch order_key: {} successfully "
"TaskingEyeAlgo::do_before_launch order_key: {} "
"successfully "
"created ChildTemplate for sec_key: {}",
serv.order_key(), sec_key);
}
LOG(OrderMan, Info,
"TaskingEyeAlgo::do_before_launch algo constructed order_key: {} "
"edge: {} shot_size: {} is_buy: {} target_qty: {} "
"max_shots_per_window: {} window_size_nanos: {} n_secs: {}",
"TaskingEyeAlgo::do_before_launch algo constructed order_key: "
"{} "
"edge: {} shot_size: {} side: {} target_qty: {} "
"max_shots_per_window: {} window_size_nanos: {} n_secs: {} "
"wait_for_signal: {} signal_file: {}",
serv.order_key(), me->edge, me->shot_size,
me->side == api::SIDE_BUY, me->target_qty,
bts::api::Side_Name(me->side), me->target_qty,
me->max_shots_per_window, me->window_size_nanos,
me->sec_map.size());
me->sec_map.size(), me->wait_for_signal, me->signal_file);
return {};
}
}
@@ -213,7 +367,8 @@ void TaskingEyeAlgo::do_launch(IAlgoServices &serv) {
for (const auto &kv : me->sec_map) {
auto sec_key = kv.first;
LOG(OrderMan, Info,
"TaskingEyeAlgo::do_before_launch subscribing book for sec_key: {}",
"TaskingEyeAlgo::do_before_launch subscribing book for sec_key: "
"{}",
sec_key);
me->service_ptr->subscribe_book(sec_key);
}
@@ -315,9 +470,9 @@ void TaskingEyeAlgo::on_theos(
if (!sec_data.driver_sec_key.has_value() ||
sec_data.driver_sec_key->value() != driver.value()) {
LOG(OrderMan, Info,
"TaskingEyeAlgo::on_theos order_key: {} subscribing to driver "
"sec_key: {}",
me->service_ptr->order_key(), driver.value());
"TaskingEyeAlgo::on_theos order_key: {} subscribing to driver for "
"sec_key: {} driver_key: {}",
me->service_ptr->order_key(), sec_key, driver.value());
sec_data.driver_sec_key = SecurityKey(driver.value());
sub_to_driver(SecurityKey(driver.value()));
}
@@ -331,8 +486,14 @@ void TaskingEyeAlgo::on_position(PositionUpdate const &pu) {
}
void TaskingEyeAlgo::_evaluate_and_shoot() {
if (me->state != TEAState::ACTIVE)
if (me->state != TEAState::ACTIVE) {
LOG(OrderMan, Warn,
"TaskingEyeAlgo::_evaluate_and_shoot order_key: {} state is not "
"active state: {} ",
me->service_ptr->order_key(), me->state);
return;
}
auto max_shots_per_win_reached = [&]() {
return n_recent_shots(me->shots, me->window_size_nanos) >=
me->max_shots_per_window;
@@ -343,12 +504,36 @@ void TaskingEyeAlgo::_evaluate_and_shoot() {
return true;
return false;
};
auto signal_condition_met = [&]() {
if (me->wait_for_signal) {
return std::filesystem::exists(me->signal_file);
} else
return true;
};
if (!signal_condition_met()) {
// Log ?
LOG(OrderMan, Warn,
"TaskingEyeAlgo::_evaluate_and_shoot order_key: {} Waiting for "
"Signal",
me->service_ptr->order_key());
return;
}
for (auto &kv : me->sec_map) {
auto sec_key = kv.first;
if (max_shots_per_win_reached())
if (max_shots_per_win_reached()) {
LOG(OrderMan, Warn,
"TaskingEyeAlgo::_evaluate_and_shoot order_key: {} "
"max_shots_per_win_reached",
me->service_ptr->order_key());
return;
if (sec_has_active_order(sec_key))
}
if (sec_has_active_order(sec_key)) {
LOG(OrderMan, Warn,
"TaskingEyeAlgo::_evaluate_and_shoot sec_key: {} has working "
"order, skipping",
sec_key.value());
continue;
}
Price price;
Quantity qty;
if (_in_shooting_zone(sec_key, price, qty)) {
@@ -357,7 +542,8 @@ void TaskingEyeAlgo::_evaluate_and_shoot() {
auto ch_ord_key =
me->service_ptr->launch_child(*sec_data.order, price, qty);
LOG(OrderMan, Info,
"TakingEyeAlgo::_evaluate_and_shoot ord_key: {} child_ord_key: {} "
"TakingEyeAlgo::_evaluate_and_shoot ord_key: {} child_ord_key: "
"{} "
"price: {} qty: {}",
me->service_ptr->order_key(), ch_ord_key, price, qty.full_value());
me->shots.push_back(TEAShot{sec_key, ch_ord_key});
@@ -375,8 +561,13 @@ bool TaskingEyeAlgo::_in_shooting_zone(
TEASecData const &sec_data = it->second;
const md::PriceSize &driver_bid = sec_data.driver_book.bid().at(0);
const md::PriceSize &driver_ask = sec_data.driver_book.ask().at(0);
if (eq(driver_bid.size(), 0) || eq(driver_ask.size(), 0))
if (eq(driver_bid.size(), 0) || eq(driver_ask.size(), 0)) {
LOG(OrderMan, Warn,
"TaskingEyeAlgo::_in_shooting_zone no driver for sec_key: {} "
"driver_bid_qty: {} driver_ask_qty: {}",
sec_key, driver_bid.size(), driver_ask.size());
return false;
}
// finding weighted mean (TODO change it to mark price later)
double driver = (driver_bid.price() * driver_bid.size() +
driver_ask.price() * driver_ask.size()) /
@@ -386,31 +577,55 @@ bool TaskingEyeAlgo::_in_shooting_zone(
// TODO normalize theo
double theo = ptp.theo();
theo = theo / driver;
if (me->side == api::SIDE_BUY) {
// auto analyze_buy =
auto analyze_buy = [&](const md::PriceSize &md_ask, double theo,
size_t level, Price &price, Quantity &qty) -> bool {
double ee_buy_pr = theo - me->edge;
const md::PriceSize &md_ask = sec_data.sec_book.ask().at(0);
LOG(OrderMan, Diag,
"TaskingEyeAlgo::_in_shooting_zone sec_key: {} driver: {} theo: {} "
"ee_price: {} md: {}/{}",
sec_key, driver, theo, ee_buy_pr, md_ask.price(), md_ask.size());
bool rv = false;
if (greater_then_eq(1, md_ask.size()) &&
greater_then_eq(md_ask.price(), ee_buy_pr)) {
price = Price(md_ask.price());
qty = std::min(static_cast<double>(md_ask.size()), me->shot_size);
return true;
qty = me->shot_size;
rv = true;
}
} else {
double ee_sell_pr = theo + me->edge;
const md::PriceSize &md_bid = sec_data.sec_book.bid().at(0);
LOG(OrderMan, Diag,
"TaskingEyeAlgo::_in_shooting_zone sec_key: {} driver: {} theo: {} "
"ee_price: {} md: {}/{}",
sec_key, driver, theo, ee_sell_pr, md_bid.price(), md_bid.size());
"TaskingEyeAlgo::_in_shooting_zone::analyze_buy sec_key: {} "
"theo: {} level: {}"
"ee_buy_pr: {} rv: {}",
sec_key, level, theo, ee_buy_pr, rv);
return rv;
};
auto analyze_sell = [&](const md::PriceSize &md_bid, double theo,
size_t level, Price &price, Quantity &qty) -> bool {
double ee_sell_pr = theo + me->edge;
bool rv = false;
if (greater_then_eq(1, md_bid.size()) &&
greater_then_eq(ee_sell_pr, md_bid.price())) {
price = Price(md_bid.price());
qty = std::min(static_cast<double>(md_bid.size()), me->shot_size);
return true;
qty = me->shot_size;
rv = true;
}
LOG(OrderMan, Diag,
"TaskingEyeAlgo::_in_shooting_zone::analyze_sell sec_key: {} theo:"
"{} level: {} ee_sell_pr: {} rv: {}",
sec_key, theo, level, ee_sell_pr, rv);
return rv;
};
if (me->side == api::SIDE_BUY) {
const auto &ask_book = sec_data.sec_book.ask();
for (int i = ask_book.size() - 1; i >= 0; i--) {
const auto &md_ask = ask_book.at(i);
if (analyze_buy(md_ask, theo, i, price, qty)) {
return true;
}
}
} else {
const auto &bid_book = sec_data.sec_book.bid();
for (int i = bid_book.size() - 1; i >= 0; i--) {
const md::PriceSize &md_bid = bid_book.at(i);
if (analyze_sell(md_bid, theo, i, price, qty)) {
return true;
}
}
}
return false;