Compare commits

...
4 Commits
16 changed files with 405 additions and 84 deletions

No files matched your search

@@ -81,9 +81,9 @@ void PositionUtilities::apply_fill (PositionEntry& pos,
const Fill& f,
bool is_add){
// strip legs are not applied to positions
if(f.fill_kind() == Fill::FILL_KIND_STRIP_LEG ||
f.fill_kind() == Fill::FILL_KIND_SOD_STRIP_LEG)
return;
// if(f.fill_kind() == Fill::FILL_KIND_STRIP_LEG ||
// f.fill_kind() == Fill::FILL_KIND_SOD_STRIP_LEG)
// return;
PositionUtilities::apply_fill(pos,
f.side(),
f.last_price(),
@@ -113,6 +113,13 @@ void PositionUtilities::total_position(const PositionUnit& p,
p.admin_day().short_pos() + p.admin_sod().short_pos();
}
void PositionUtilities::day_position(const PositionUnit& p,
uint32_t& total_long,
uint32_t& total_short){
total_long = p.day().long_pos() + p.admin_day().long_pos();
total_short = p.day().short_pos() + p.admin_day().short_pos();
}
PositionEntry PositionUtilities::add_positions (
const std::vector<PositionEntry const*>& v){
double long_pos = 0, long_price = 0, short_pos = 0, short_price = 0;
@@ -39,6 +39,10 @@ public:
uint32_t& total_long,
uint32_t& total_short);
static void day_position(const bts::api::position::PositionUnit& p,
uint32_t& total_long,
uint32_t& total_short);
static bts::api::position::PositionEntry add_positions (
const std::vector<bts::api::position::PositionEntry const*>& v);
+64 -5
View File
@@ -219,13 +219,17 @@ inline bool test_price_equality(double price_1, double price_2) {
// ice specialization /////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
inline bool is_ice_spread(const bts::api::ICEExchangeData& ice_data){
const auto& strip = ice_data.strip_name();
return strip.find("/") != std::string::npos;
}
// TODO, move this to its own file
static const uint32_t days_in_month[] = {31,28,31,30,31,30,31,31,30,31,30,31};
inline bool is_strip(const bts::api::Security& sec){
return sec.has_ice() && sec.has_spread() && !is_ice_spread(sec.ice());
auto rv = sec.has_ice() && sec.has_spread() &&
sec.ice().strip_name().find("/") != std::string::npos;
if(rv != sec.ice().is_strip())
LOG(OrderMan, Error, "Sec:{} is_strip incorrect, expected:{}",
sec.id(),rv);
return rv;
}
inline bool is_spread_of_strips(const bts::api::Security& sec){
@@ -239,5 +243,60 @@ inline bool is_spread_of_strips(const bts::api::Security& sec){
return false;
}
inline bool is_strip_with_no_leg_fills(const bts::api::Security& sec){
return sec.has_ice() &&
sec.symbol() == "HHD" &&
is_strip(sec);
}
inline bool is_daily_contract(const api::Security& s){
return s.symbol() == "HHD";
}
// risk qty is the qty used for risk purposes for
// 1 lot of this security.
// daily futures and ice lots are considered 1,
// other futures (HNG,HIS,HEN etc) are the days
// in the month
inline double get_fut_risk_qty(const api::Security& s){
bts_assert(s.has_future());
bts_assert(s.has_ice());
if(is_daily_contract(s))
return 1;
else if(s.ice().is_ice_lots())
return 1;
else{
auto mm = s.future().expiration_month();
if(mm <=11)
return days_in_month[mm];
else{
LOG(persist, Error, "Illegal month:{} for sec:{}",
mm, s.id());
// TODO
return 0.0;
}
}
}
inline double get_spread_direct_risk_qty(const api::Security& s){
bts_assert(s.has_spread());
bts_assert(s.has_ice());
if(s.ice().is_ice_lots())
return 1;
else
return 30;
}
inline double get_risk_qty (const api::Security& s){
if(s.has_future())
return get_fut_risk_qty(s);
else if (s.has_spread())
return get_spread_direct_risk_qty(s);
else{
bts_assert(false);
return 0.0;
}
}
} // namespace securities
} // namespace bts
+3
View File
@@ -116,6 +116,9 @@ message ICEExchangeData {
string strip_name = 140;
string mic_code = 150;
bool is_strip = 160;
bool is_ice_lots = 170;
// for example 2500 for HNG, this is the qty the exchange needs to see
double exchange_qty_ratio = 180;
}
message FutureContract {
+17 -1
View File
@@ -35,6 +35,7 @@ FixedSecurity::FixedSecurity() :
ice_deal_denominator(0),
ice_settle_denominator(0),
is_ice_strip(false),
is_ice_lots(false),
_symbol(),
_cfi_code(),
_security_desc(),
@@ -98,6 +99,19 @@ void write_leg_descriptor(std::ostringstream& result, char separator, const api:
<< leg.leg_delta() << (leg.side() == SIDE_BUY ? "BUY" : "SELL");
}
// for ice sec's it populates exchange_qty_ratio and
// net_
void populate_derivable_fields (api::Security& s){
if (s.has_ice()){
auto& ice = *s.mutable_ice();
if(!ice.is_ice_lots())
// TODO : get this from config
ice.set_exchange_qty_ratio(2500);
else
ice.set_exchange_qty_ratio(1.0);
}
}
std::string security_descriptor(const api::Security& security) {
const char separator = ' ';
std::ostringstream result;
@@ -197,6 +211,7 @@ void convert_security(const FixedSecurity& source, Security* presult) {
dest.mutable_ice()->set_strip_name(source.ice_strip_name());
dest.mutable_ice()->set_mic_code(source.mic_code());
dest.mutable_ice()->set_is_strip(source.is_ice_strip);
dest.mutable_ice()->set_is_ice_lots(source.is_ice_lots);
break;
case Venue::VENUE_BTS:
break;
@@ -253,8 +268,8 @@ void convert_security(const FixedSecurity& source, Security* presult) {
case SecurityType_INT_MIN_SENTINEL_DO_NOT_USE_:
break;
}
get_contract_spec(source, *dest.mutable_contract_spec());
populate_derivable_fields(*presult);
}
SecurityType securitytype_of_contractcase(Security::ContractCase cc) {
@@ -323,6 +338,7 @@ void convert_security(const Security& src, FixedSecurity& dest, bool set_securit
dest.ice_deal_denominator = src.ice().deal_denominator();
dest.ice_settle_denominator = src.ice().settle_denominator();
dest.is_ice_strip = src.ice().is_strip();
dest.is_ice_lots = src.ice().is_ice_lots();
dest.ice_market_description(src.ice().market_description());
dest.ice_cleared_alias(src.ice().cleared_alias());
+1
View File
@@ -63,6 +63,7 @@ public:
int8_t ice_deal_denominator;
int8_t ice_settle_denominator;
bool is_ice_strip;
bool is_ice_lots;
STRING_PROPERTY(ice_market_description);
STRING_PROPERTY(ice_cleared_alias);
+3 -3
View File
@@ -263,9 +263,9 @@ make_pos_event(api::position::PositionUnit const &pu,
auto sec = o.resolve(SecurityKey(pu.security_id()));
if (sec) {
uint32_t total_long_pos, total_short_pos;
bts::position::PositionUtilities::total_position(pu,
total_long_pos,
total_short_pos);
bts::position::PositionUtilities::day_position(pu,
total_long_pos,
total_short_pos);
FilledPositionEvent ev{
*sec, AccountID(pu.account_id()),
Quantity(total_long_pos),
+129 -31
View File
@@ -13,6 +13,7 @@
#include "lib/os/os.h"
#include "lib/config/config.h"
#include "lib/config/protos/span.pb.h"
#include "lib/domain_model/Security.h"
namespace bts {
@@ -125,9 +126,34 @@ public:
AccountID account_id,
Quantity long_pos,
Quantity short_pos){
LOG(OrderMan, Info,
"LimitStore::set_filled_position sec:{} sym:{} acc:{} "
"long:{} short:{}",
security.id(), security.symbol(), account_id, long_pos, short_pos);
SecurityLimitTree* sec_tree = _find_security(security.id());
bts_assert(sec_tree != nullptr);
sec_tree->update_position(account_id, long_pos, short_pos);
// there are some strips where we dont store the leg
// fills (because the exchange does not give us that, for
// example the HHD strip of daily futures). in this case,
// we add it manually here
if(bts::securities::is_strip_with_no_leg_fills(security)){
bts_assert(security.has_spread());
for (const auto& l : security.spread().legs()){
auto lside = l.side();
auto lqty = l.quantity();
Quantity llong, lshort;
if(lside == api::Side::SIDE_BUY){
llong = Quantity(long_pos.value() * lqty);
lshort = Quantity(short_pos.value() * lqty);
}
else{
llong = Quantity(short_pos.value() * lqty);
lshort = Quantity(long_pos.value() * lqty);
}
set_filled_position(l.security(),account_id,llong,lshort);
}
}
}
void set_pnl (AccountID account_id, double pnl);
void set_excess_liquidity (AccountID account_id, double excess_liquidity);
@@ -174,7 +200,7 @@ public:
const api::Security& security,
api::Side side,
Quantity quantity,
bool enforce_clip = true) const{
bool enforce_clip = true){
Limiter::LimiterQueryResult rv; rv.result = Limiter::OK;
const auto acct_it = _account_data_map.find(account_id.value());
AccountData const* account_ptr = (acct_it == _account_data_map.end()) ?
@@ -185,33 +211,21 @@ public:
_margin_limit(account_ptr,rv);
if(!rv.ok())
return rv;
const SecurityLimitTree* sec_tree = _find_security(security.id());
SecurityLimitTree* sec_tree = _find_security(security.id());
bts_assert(sec_tree != nullptr);
rv = sec_tree->can_send_new_order(account_id, side, quantity, enforce_clip);
if (rv.ok() && security.has_spread() && sec_tree->is_residual_pos_spread()){
// if its rejected we dont need to validate further,
// however if its ok and its a spread we need to check
// the legs
for (const api::ContractLeg& leg : security.spread().legs()){
api::Side effective_leg_side = side == api::Side::SIDE_BUY ? leg.side() : LimiterUtils::invert_side(leg.side());
Quantity effective_leg_qty = Quantity (quantity.value() * std::abs(leg.quantity()));
// in the case of a spread with future legs the clip limits
// for the legs are not checked. if the legs are are spreads
// themselves then they are checked (eventually it will recuse
// down to a non spread)
rv = can_send_new_order (account_id, leg.security(), effective_leg_side, effective_leg_qty,
leg.security().has_spread());
if (!rv.ok())
return rv;
}
}
if (rv.ok() && security.has_spread())
return _check_new_order_pos_violations_recursively(security,
account_id,
side,
quantity);
return rv;
}
Limiter::LimiterQueryResult can_update_order(AccountID account_id,
const api::Security& security,
api::Side side,
Quantity from, Quantity to,
bool enforce_clip = true) const{
bool enforce_clip = true){
Limiter::LimiterQueryResult rv; rv.result = Limiter::OK;
const auto acct_it = _account_data_map.find(account_id.value());
AccountData const* account_ptr = (acct_it == _account_data_map.end()) ?
@@ -226,17 +240,12 @@ public:
bts_assert(sec_tree != nullptr);
rv = sec_tree->can_update_order(account_id, side, from, to, enforce_clip);
// same logic for the spread legs as above
if (rv.ok() && security.has_spread() && sec_tree->is_residual_pos_spread()){
for (const api::ContractLeg& leg : security.spread().legs()){
api::Side effective_leg_side = side == api::Side::SIDE_BUY ? leg.side() : LimiterUtils::invert_side(leg.side());
Quantity effective_leg_from_qty = Quantity (from.value() * std::abs(leg.quantity()));
Quantity effective_leg_to_qty = Quantity (to.value() * std::abs(leg.quantity()));
rv = can_update_order (account_id, leg.security(), effective_leg_side, effective_leg_from_qty, effective_leg_to_qty,
leg.security().has_spread());
if (!rv.ok())
return rv;
}
}
if (rv.ok() && security.has_spread())
return _check_order_update_pos_violations_recursively(security,
account_id,
side,
from,
to);
return rv;
}
// test only functions
@@ -320,5 +329,94 @@ private:
out_res.error_msg = "Could not find account";
}
}
// 07/16/2020
// adds the working order to the current state.
// if s is a spread, it iterates through the legs and
// adds the legs with the appropriate side, qty
void _add_or_remove_working_order(const api::Security& s,
AccountID acc,
api::Side side,
Quantity qty,
bool is_add,
bool real_op){
LOG(OrderMan, Info,
"LimitStore::_add_or_remove_working_order "
"real_op:{} is_add:{} sec:{} acc:{} side:{} qty:{} ",
real_op, is_add, s.id(), acc, side, qty);
SecurityLimitTree* sec_tree = _find_security(s.id());
if(is_add)
sec_tree->add_working_order(acc,side,qty,real_op);
else
sec_tree->remove_working_order(acc,side,qty,real_op);
if(s.has_spread()){
for (const auto& l : s.spread().legs()){
const auto& lsec = l.security();
api::Side eff_leg_side = side == api::Side::SIDE_BUY ? l.side() :
LimiterUtils::invert_side(l.side());
Quantity eff_leg_qty = Quantity (qty.value() * std::abs(l.quantity()));
_add_or_remove_working_order(lsec,acc,eff_leg_side,eff_leg_qty,is_add,real_op);
}
}
}
// for the current state of the positions, check the violations
// on the side passed in the arg
Limiter::LimiterQueryResult _check_curr_pos_violations_recursively(
const api::Security& s,
AccountID acc,
api::Side side){
SecurityLimitTree* sec_tree = _find_security(s.id());
auto rv =
sec_tree->can_send_new_order(acc,side,Quantity(0),false);
if(rv.ok()){
if(s.has_spread()){
for (const auto& l : s.spread().legs()){
const auto& lsec = l.security();
api::Side eff_leg_side = side == api::Side::SIDE_BUY ? l.side() :
LimiterUtils::invert_side(l.side());
auto l_rv =_check_curr_pos_violations_recursively(lsec,acc,eff_leg_side);
if(!l_rv.ok())
return l_rv;
}
}
}
return rv;
}
Limiter::LimiterQueryResult _check_new_order_pos_violations_recursively(
const api::Security& s,
AccountID acc,
api::Side side,
Quantity qty){
_add_or_remove_working_order(s,acc,side,qty,true,false);
auto rv = _check_curr_pos_violations_recursively(s,acc,side);
_add_or_remove_working_order(s,acc,side,qty,false,false);
return rv;
}
Limiter::LimiterQueryResult _check_order_update_pos_violations_recursively(
const api::Security& s,
AccountID acc,
api::Side side,
Quantity from,
Quantity to){
if(to.value() > from.value()){
// this is the equivelant of a new order of
// qty = to-from, on the same side
auto eff_qty = Quantity(to.value()-from.value());
auto eff_side = side;
return _check_new_order_pos_violations_recursively(s,acc,eff_side,eff_qty);
}
else{
// this is the equivelant of a new order of
// qty = from-to, on the opp side
auto eff_qty = Quantity(from.value()-to.value());
auto eff_side = LimiterUtils::invert_side(side);
return _check_new_order_pos_violations_recursively(s,acc,eff_side,eff_qty);
}
}
};
}}
+10
View File
@@ -42,6 +42,10 @@ void Limiter::set_filled_position(api::Security const &security,
// add_securities was called or before add was called
// (we can put it in a queue and examine it on the
// add_securities call)
LOG(OrderMan, Info, "Limiter::set_filled_position sec:{} sym:{} acc:{} "
"long:{} short:{}",
security.id(),security.symbol(),account_id,
long_pos,short_pos);
if (!_contains_security(&security))
LOG(OrderMan, Error, "Limiter::set_filled_position could not find"
"sec_key:{} this should be investigated",
@@ -59,6 +63,9 @@ void Limiter::add_to_working_position(api::Security const &security,
AccountID account_id,
api::Side side,
Quantity quantity){
LOG(OrderMan, Info, "Limiter::add_to_working_position "
"sec:{} acc:{} side:{} qty:{}",
security.id(),account_id,side,quantity);
if (!_contains_security(&security))
add_security(security);
if (!me->limit_store->has_account(account_id)) {
@@ -71,6 +78,9 @@ void Limiter::remove_from_working_position(api::Security const &security,
AccountID account_id,
api::Side side,
Quantity quantity) {
LOG(OrderMan, Info, "Limiter::remove_from_working_position "
"sec:{} acc:{} side:{} qty:{}",
security.id(),account_id,side,quantity);
if (!_contains_security(&security))
add_security(security);
if (!me->limit_store->has_account(account_id)) {
+3 -6
View File
@@ -35,7 +35,9 @@ class Limiter {
// Set the current *filled* position, i.e. actual number of
// contracts owned, for the specified sec and account. Spreads are
// not recursively deconstructed into their legs, i.e this sets the
// position only for the passed security.
// position only for the passed security
// (this function is called by the framework for the
// the legs)
void set_filled_position(api::Security const &, AccountID,
Quantity long_pos,
Quantity short_pos);
@@ -48,11 +50,6 @@ class Limiter {
void remove_from_working_position(api::Security const &, AccountID,
api::Side, Quantity);
void update_position(const api::position::PositionUnit &);
// this can be a partial list
void update_position(
const std::vector<api::position::PositionUnit const *> &positions);
void set_day_pnl(AccountID account_id, double pnl);
void set_excess_liquidity(AccountID account_id, double excesss_liquidity);
+52 -16
View File
@@ -2,6 +2,7 @@
#include <memory>
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/api/services/OrderService.pb.h"
#include "Limiter.h"
namespace bts{
@@ -9,12 +10,36 @@ namespace orderman{
class LimiterUtils{
public:
static std::string product_string(const bts::api::admin::ProductScope& p){
auto _type = [&]() -> std::string {
switch(p.security_type()){
case api::SecurityType::SECURITYTYPE_UNSET:
return "UNSET";
case api::SecurityType::SECURITYTYPE_STOCK:
return "STOCK";
case api::SecurityType::SECURITYTYPE_OPTION:
return "OPTION";
case api::SecurityType::SECURITYTYPE_FUTURE:
return "FUTURE";
case api::SecurityType::SECURITYTYPE_SPREAD:
return "SPREAD";
case api::SecurityType::SECURITYTYPE_SYNTHETIC:
return "SYNTHETIC";
default:
return "UNSET";
}
return "UNSET";
};
return p.security_symbol() + " " + _type();
}
static void can_send_new_order_net(api::Side side,
Quantity quantity,
bool is_security_level,
int long_pos_limit,
int short_pos_limit,
int net_pos,
const bts::api::admin::ProductScope& p,
Limiter::LimiterQueryResult& out_res){
out_res.result = Limiter::OK;
bts_assert(long_pos_limit >= 0);
@@ -31,7 +56,9 @@ public:
}
else {
out_res.result = Limiter::PROD_LONG_POS_BREACHED;
out_res.error_msg = fmt::format("Product Long Position (Max {} Current {})", long_pos_limit, net_pos);
out_res.error_msg = fmt::format(
"Product Long Position for {} Max {} ({})",
product_string(p), long_pos_limit, net_pos_new);
}
}
else if (net_pos_new < short_pos_limit && side==api::Side::SIDE_SELL){
@@ -41,7 +68,9 @@ public:
}
else {
out_res.result = Limiter::PROD_SHORT_POS_BREACHED;
out_res.error_msg = fmt::format("Product Short Position (Max {} Current {})", short_pos_limit, net_pos);
out_res.error_msg = fmt::format(
"Product Short Position for {} Max {} ({})",
product_string(p), short_pos_limit, net_pos_new);
}
}
}
@@ -52,6 +81,7 @@ public:
int long_pos_limit,
int short_pos_limit,
int net_pos,
const bts::api::admin::ProductScope& p,
Limiter::LimiterQueryResult& out_res){
out_res.result = Limiter::OK;
if (to.value() > from.value()){
@@ -68,7 +98,9 @@ public:
}
else {
out_res.result = Limiter::PROD_LONG_POS_BREACHED;
out_res.error_msg = fmt::format("Product Long Position (Max {} Current {})", long_pos_limit, net_pos);
out_res.error_msg = fmt::format(
"Product Long Position for {} Max {} ({})",
product_string(p), long_pos_limit, net_pos_new);
}
}
else if (net_pos_new < short_pos_limit){
@@ -78,7 +110,9 @@ public:
}
else {
out_res.result = Limiter::SEC_SHORT_POS_BREACHED;
out_res.error_msg = fmt::format("Product Short Position (Max {} Current {})", short_pos_limit, net_pos);
out_res.error_msg = fmt::format(
"Product Short Position for {} Max {} ({})",
product_string(p), short_pos_limit, net_pos_new);
}
}
}
@@ -89,21 +123,22 @@ public:
uint32_t short_pos_limit,
uint32_t long_pos,
uint32_t short_pos,
const bts::api::admin::ProductScope& p,
Limiter::LimiterQueryResult& out_res){
out_res.result = Limiter::OK;
if(side == api::Side::SIDE_BUY){
if(long_pos + quantity.value() > long_pos_limit){
out_res.error_msg = fmt::format("Long Position (Abs) "
"(Max {} Current {})",
long_pos_limit,long_pos);
out_res.error_msg = fmt::format("Long Position (Abs) for {} "
"(Max {} {})",
product_string(p),long_pos_limit,long_pos + quantity.value());
out_res.result = Limiter::SEC_LONG_POS_BREACHED;
}
}
else{
if(short_pos + quantity.value() > short_pos_limit){
out_res.error_msg = fmt::format("Short Position (Abs) "
"(Max {} Current {})",
short_pos_limit,short_pos);
out_res.error_msg = fmt::format("Short Position (Abs) for {}"
"(Max {} {})",
product_string(p),short_pos_limit,short_pos + quantity.value());
out_res.result = Limiter::SEC_SHORT_POS_BREACHED;
}
}
@@ -115,23 +150,24 @@ public:
uint32_t short_pos_limit,
uint32_t long_pos,
uint32_t short_pos,
const bts::api::admin::ProductScope& p,
Limiter::LimiterQueryResult& out_res){
out_res.result = Limiter::OK;
if (to.value() > from.value()){
uint32_t diff = to.value() - from.value();
if(side == api::Side::SIDE_BUY){
if(long_pos + diff > long_pos_limit){
out_res.error_msg = fmt::format("Long Position (Abs) "
"(Max {} Current {})",
long_pos_limit,long_pos);
out_res.error_msg = fmt::format("Long Position (Abs) for {}"
"(Max {} {})",
product_string(p),long_pos_limit,long_pos);
out_res.result = Limiter::SEC_LONG_POS_BREACHED;
}
}
else{
if(short_pos + diff > short_pos_limit){
out_res.error_msg = fmt::format("Short Position (Abs) "
"(Max {} Current {})",
short_pos_limit,short_pos);
out_res.error_msg = fmt::format("Short Position (Abs) for {}"
"(Max {} {})",
product_string(p),short_pos_limit,short_pos + diff);
out_res.result = Limiter::SEC_SHORT_POS_BREACHED;
}
}
+27 -3
View File
@@ -46,17 +46,37 @@ public:
_long_pos=source._long_pos;
_short_pos=source._short_pos;
}
void add_position(api::Side side, uint32_t qty){
// real_op in both the functions below is for logging purposes only
void add_position(api::Side side, uint32_t qty, bool real_op=true){
if (side==api::Side::SIDE_BUY)
_long_pos += qty;
else
_short_pos += qty;
}
void remove_position(api::Side side, uint32_t qty){
LOG(OrderMan, Info, "ProductLimitNode::position "
"real_op:{} add type:{} sym:{} "
"side:{} qty:{} "
"long_pos:{} short_pos:{}",
real_op,
_limit.product().security_type(),
_limit.product().security_symbol(),
side, qty,
_long_pos, _short_pos);
}
void remove_position(api::Side side, uint32_t qty, bool real_op=true){
if (side==api::Side::SIDE_BUY)
_long_pos = _subtract_unsigned(_long_pos, qty);
else
_short_pos = _subtract_unsigned(_short_pos, qty);
LOG(OrderMan, Info, "ProductLimitNode::position "
"real_op:{} remove type:{} sym:{}"
"side:{} qty:{} "
"long_pos:{} short_pos:{}",
real_op,
_limit.product().security_type(),
_limit.product().security_symbol(),
side, qty,
_long_pos, _short_pos);
}
// risk limit checks
////////////////////
@@ -75,6 +95,7 @@ public:
static_cast<int>(_long_pos_limit),
-1 * static_cast<int>(_short_pos_limit),
net_pos(),
_limit.product(),
result);
else
LimiterUtils::can_send_new_order_abs (side,
@@ -83,6 +104,7 @@ public:
_short_pos_limit,
_long_pos,
_short_pos,
_limit.product(),
result);
return result;
}
@@ -103,6 +125,7 @@ public:
static_cast<int>(_long_pos_limit),
-1 * static_cast<int>(_short_pos_limit),
net_pos(),
_limit.product(),
result);
else
LimiterUtils::can_update_order_abs (side,
@@ -112,6 +135,7 @@ public:
_short_pos_limit,
_long_pos,
_short_pos,
_limit.product(),
result);
return result;
}
+6 -4
View File
@@ -18,11 +18,13 @@ namespace bts{
namespace orderman{
// Scope : <security, account>
// Created by : SecurityLimitTree
// Keeps the running position and clip limits
// for a single <security, account>.
// Has a reference to the ProductLimitNode for the product
// that this security belongs to.
// Position limits are enforced through the ProductLimitNode
// and clip limits are enforced here
class SecurityLimitNode{
private:
const uint32_t _account_id;
@@ -54,11 +56,11 @@ public:
// simplicity's sake we do this
_product_node.copy_positions_from(source._product_node);
}
void add_working_order(api::Side side, uint32_t qty){
_product_node.add_position(side, qty);
void add_working_order(api::Side side, uint32_t qty, bool real_op=true){
_product_node.add_position(side, qty, real_op);
}
void remove_working_order(api::Side side, uint32_t qty){
_product_node.remove_position(side, qty);
void remove_working_order(api::Side side, uint32_t qty, bool real_op=true){
_product_node.remove_position(side, qty, real_op);
}
void update_position (AccountID account_id,
Quantity long_pos,
+14 -2
View File
@@ -7,14 +7,26 @@ using namespace bts::admin;
namespace bts {
namespace orderman {
SecurityLimitTree::QtyNormalization get_qty_norm (const api::Security& s){
if(s.has_ice())
return SecurityLimitTree::QtyNormalization
{
s.ice().exchange_qty_ratio(),
bts::securities::get_risk_qty(s)
};
else
return SecurityLimitTree::QtyNormalization {1,1};
}
SecurityLimitTree::SecurityLimitTree(
const api::Security &security,
ProductLimitTree &product_tree,
const AdminData &admin_data)
:
_sec_key(security.id()),
_is_residual_pos_spread(
bts::admin::AdminServiceUtils::residual_pos_spread(security))
{
bts::admin::AdminServiceUtils::residual_pos_spread(security)),
_qty_norm(get_qty_norm(security)){
for (const auto &account : admin_data.accounts()) {
ProductLimitNode *product_node_ptr =
product_tree.find_account(account.id());
+60 -8
View File
@@ -14,37 +14,81 @@
namespace bts{
namespace orderman{
// Scope : <security>
// this keeps the state a single security for all accounts
// Created by : LimitStore
// this keeps the state of a single security for all accounts
// The only state is a vector of SecurityLimitNode (which keeps
// the actual data)
class SecurityLimitTree{
public:
// This is to handle the condition when the risk control
// is entered in different terms than what the order management
// system (and exchange) deal with.
// This is a 2 step process
// exchange_qty_ratio : exchange deals with qty's that are
// different than what the ui (both trading and risk)
// deals with. for example HNG at ICE
// is multiple of 2500, but the ui shows it
// as 1, 5000 as 2 etc.
// unit_risk_qty : this is purely for risk reasons. for example
// the risk for HNG is entered in terms of the ICE lot,
// for HHD it is entered in terms of the daily future etc.
struct QtyNormalization{
const double exchange_qty_ratio = 1;
const double unit_risk_qty = 1;
};
private:
const SecurityKey _sec_key;
const bool _is_residual_pos_spread;
const QtyNormalization _qty_norm;
std::vector<SecurityLimitNode> _tree;
public:
public:
SecurityLimitTree(
const api::Security& security,
ProductLimitTree& product_tree,
const bts::api::admin::AdminData& admin_data);
// position updates ////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// real_op is used for logging purposes in all the functions below
void add_working_order(AccountID account_id,
api::Side side,
Quantity quantity){
Quantity quantity_arg,
bool real_op=true){
auto quantity = _convert_qty(quantity_arg);
LOG(OrderMan, Info, "SecurityLimitTree::add_working_order "
"real_op:{} sec:{} acc:{} "
"side:{} qty_arg:{} qty:{}",
real_op, _sec_key, account_id,
side, quantity_arg, quantity);
SecurityLimitNode* node_ptr = _find_account(account_id.value());
bts_assert(node_ptr != nullptr);
node_ptr->add_working_order(side, quantity.value());
node_ptr->add_working_order(side, quantity.value(),real_op);
}
void remove_working_order(AccountID account_id,
api::Side side,
Quantity quantity){
Quantity quantity_arg,
bool real_op=true){
auto quantity = _convert_qty(quantity_arg);
LOG(OrderMan, Info, "SecurityLimitTree::remove_working_order "
"real_op:{} sec:{} acc:{} "
"side:{} qty_arg:{} qty:{}",
real_op, _sec_key, account_id,
side, quantity_arg, quantity);
SecurityLimitNode* account_ptr = _find_account(account_id.value());
bts_assert(account_ptr != nullptr);
account_ptr->remove_working_order(side, quantity.value());
account_ptr->remove_working_order(side, quantity.value(),real_op);
}
void update_position (AccountID account_id,
Quantity long_pos,
Quantity short_pos){
Quantity long_pos_arg,
Quantity short_pos_arg){
auto long_pos = _convert_qty(long_pos_arg);
auto short_pos = _convert_qty(short_pos_arg);
LOG(OrderMan, Info, "SecurityLimitTree::update_position "
"sec:{} acc:{} "
"long_pos_arg:{} long_pos:{} "
"short_pos_arg:{} short_pos:{} ",
_sec_key, account_id,
long_pos_arg, long_pos,
short_pos_arg, short_pos);
SecurityLimitNode* account_ptr = _find_account(account_id.value());
bts_assert(account_ptr != nullptr);
account_ptr->update_position(account_id, long_pos, short_pos);
@@ -66,6 +110,7 @@ public:
Quantity quantity,
bool enforce_clip = true)
const{
quantity = _convert_qty(quantity);
const SecurityLimitNode* node_ptr = _find_account(account_id.value());
bts_assert(node_ptr!=nullptr);
return node_ptr->can_send_new_order(side, quantity, enforce_clip);
@@ -76,6 +121,8 @@ public:
Quantity to,
bool enforce_clip = true)
const{
from = _convert_qty(from);
to = _convert_qty(to);
const SecurityLimitNode* node_ptr = _find_account(account_id.value());
bts_assert(node_ptr!=nullptr);
return node_ptr->can_update_order(side, from, to, enforce_clip);
@@ -101,5 +148,10 @@ private:
return &node;
return nullptr;
}
Quantity _convert_qty (Quantity qty) const{
auto v = qty.value();
auto v_out =(v/_qty_norm.exchange_qty_ratio)*_qty_norm.unit_risk_qty;
return Quantity(v_out);
}
};
}}
@@ -184,13 +184,13 @@ void ICEListener::set_security_common(const Definition& def, FixedSecurity& fsec
double min_tick = double_of_price(def.incrementPrice, fsec.ice_price_denominator);
fsec.low_tick = fsec.high_tick = min_tick;
fsec.price_break = 0.0;
fsec.is_ice_lots = def.marketDesc.find("ICE Lots") != std::string::npos;
fsec.ice_market_description(def.marketDesc);
fsec.ice_cleared_alias(def.clearedAlias);
fsec.ice_product_name(def.productName);
fsec.ice_hub_alias(def.hubAlias);
fsec.ice_strip_name(def.stripName);
fsec.mic_code(def.micCode);
fsec.mic_code(def.micCode);
}
template<typename Definition>