BitShares-Core  7.0.2
BitShares blockchain node software and command-line wallet software
grouped_orders_plugin.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2018 Abit More, and contributors.
3  *
4  * The MIT License
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 
26 
28 
29 namespace graphene { namespace grouped_orders {
30 
31 namespace detail
32 {
33 
35 {
36  public:
38  :_self( _plugin ) {}
39 
41  {
42  return _self.database();
43  }
44 
46  flat_set<uint16_t> _tracked_groups;
47 };
48 
53 {
54  public:
55  limit_order_group_index( const flat_set<uint16_t>& groups ) : _tracked_groups( groups ) {};
56 
57  virtual void object_inserted( const object& obj ) override;
58  virtual void object_removed( const object& obj ) override;
59  virtual void about_to_modify( const object& before ) override;
60  virtual void object_modified( const object& after ) override;
61 
62  const flat_set<uint16_t>& get_tracked_groups() const
63  { return _tracked_groups; }
64 
65  const map< limit_order_group_key, limit_order_group_data >& get_order_groups() const
66  { return _og_data; }
67 
68  private:
69  void remove_order( const limit_order_object& obj, bool remove_empty = true );
70 
72  flat_set<uint16_t> _tracked_groups;
73 
75  map< limit_order_group_key, limit_order_group_data > _og_data;
76 };
77 
78 void limit_order_group_index::object_inserted( const object& objct )
79 { try {
80  const limit_order_object& o = static_cast<const limit_order_object&>( objct );
81 
82  auto& idx = _og_data;
83 
84  for( uint16_t group : get_tracked_groups() )
85  {
86  auto create_ogo = [&]() {
88  };
89  // if idx is empty, insert this order
90  // Note: not capped
91  if( idx.empty() )
92  {
93  create_ogo();
94  continue;
95  }
96 
97  // cap the price
98  price capped_price = o.sell_price;
99  price max = o.sell_price.max();
100  price min = o.sell_price.min();
101  bool capped_max = false;
102  bool capped_min = false;
103  if( o.sell_price > max )
104  {
105  capped_price = max;
106  capped_max = true;
107  }
108  else if( o.sell_price < min )
109  {
110  capped_price = min;
111  capped_min = true;
112  }
113  // if idx is not empty, find the group that is next to this order
114  auto itr = idx.lower_bound( limit_order_group_key( group, capped_price ) );
115  bool check_previous = false;
116  if( itr == idx.end() || itr->first.group != group
117  || itr->first.min_price.base.asset_id != o.sell_price.base.asset_id
118  || itr->first.min_price.quote.asset_id != o.sell_price.quote.asset_id )
119  // not same market or group type
120  check_previous = true;
121  else // same market and group type
122  {
123  bool update_max = false;
124  if( capped_price > itr->second.max_price ) // implies itr->min_price <= itr->max_price < max
125  {
126  update_max = true;
127  price max_price = itr->first.min_price * ratio_type( GRAPHENE_100_PERCENT + group, GRAPHENE_100_PERCENT );
128  // max_price should have been capped here
129  if( capped_price > max_price ) // new order is out of range
130  check_previous = true;
131  }
132  if( !check_previous ) // new order is within the range
133  {
134  if( capped_min && o.sell_price < itr->first.min_price )
135  { // need to update itr->min_price here, if itr is below min, and new order is even lower
136  // TODO improve performance
137  limit_order_group_data data( itr->second.max_price, o.for_sale + itr->second.total_for_sale );
138  idx.erase( itr );
139  idx[ limit_order_group_key( group, o.sell_price ) ] = data;
140  }
141  else
142  {
143  if( update_max || ( capped_max && o.sell_price > itr->second.max_price ) )
144  itr->second.max_price = o.sell_price; // store real price here, not capped
145  itr->second.total_for_sale += o.for_sale;
146  }
147  }
148  }
149 
150  if( check_previous )
151  {
152  if( itr == idx.begin() ) // no previous
153  create_ogo();
154  else
155  {
156  --itr; // should be valid
157  if( itr->first.group != group || itr->first.min_price.base.asset_id != o.sell_price.base.asset_id
158  || itr->first.min_price.quote.asset_id != o.sell_price.quote.asset_id )
159  // not same market or group type
160  create_ogo();
161  else // same market and group type
162  {
163  // due to lower_bound, always true: capped_price < itr->first.min_price, so no need to check again,
164  // if new order is in range of itr group, always need to update itr->first.min_price, unless
165  // o.sell_price is higher than max
166  price min_price = itr->second.max_price / ratio_type( GRAPHENE_100_PERCENT + group, GRAPHENE_100_PERCENT );
167  // min_price should have been capped here
168  if( capped_price < min_price ) // new order is out of range
169  create_ogo();
170  else if( capped_max && o.sell_price >= itr->first.min_price )
171  { // itr is above max, and price of new order is even higher
172  if( o.sell_price > itr->second.max_price )
173  itr->second.max_price = o.sell_price;
174  itr->second.total_for_sale += o.for_sale;
175  }
176  else
177  { // new order is within the range
178  // TODO improve performance
179  limit_order_group_data data( itr->second.max_price, o.for_sale + itr->second.total_for_sale );
180  idx.erase( itr );
181  idx[ limit_order_group_key( group, o.sell_price ) ] = data;
182  }
183  }
184  }
185  }
186  }
187 } FC_CAPTURE_AND_RETHROW( (objct) ); }
188 
189 void limit_order_group_index::object_removed( const object& objct )
190 { try {
191  const limit_order_object& o = static_cast<const limit_order_object&>( objct );
192  remove_order( o );
193 } FC_CAPTURE_AND_RETHROW( (objct) ); }
194 
195 void limit_order_group_index::about_to_modify( const object& objct )
196 { try {
197  const limit_order_object& o = static_cast<const limit_order_object&>( objct );
198  remove_order( o, false );
199 } FC_CAPTURE_AND_RETHROW( (objct) ); }
200 
201 void limit_order_group_index::object_modified( const object& objct )
202 { try {
203  object_inserted( objct );
204 } FC_CAPTURE_AND_RETHROW( (objct) ); }
205 
206 void limit_order_group_index::remove_order( const limit_order_object& o, bool remove_empty )
207 {
208  auto& idx = _og_data;
209 
210  for( uint16_t group : get_tracked_groups() )
211  {
212  // find the group that should contain this order
213  auto itr = idx.lower_bound( limit_order_group_key( group, o.sell_price ) );
214  if( itr == idx.end() || itr->first.group != group
215  || itr->first.min_price.base.asset_id != o.sell_price.base.asset_id
216  || itr->first.min_price.quote.asset_id != o.sell_price.quote.asset_id
217  || itr->second.max_price < o.sell_price )
218  {
219  // can not find corresponding group, should not happen
220  wlog( "can not find the order group containing order for removing (price dismatch): ${o}", ("o",o) );
221  continue;
222  }
223  else // found
224  {
225  if( itr->second.total_for_sale < o.for_sale )
226  // should not happen
227  wlog( "can not find the order group containing order for removing (amount dismatch): ${o}", ("o",o) );
228  else if( !remove_empty || itr->second.total_for_sale > o.for_sale )
229  itr->second.total_for_sale -= o.for_sale;
230  else
231  // it's the only order in the group and need to be removed
232  idx.erase( itr );
233  }
234  }
235 }
236 
237 } // end namespace detail
238 
239 
241  plugin(app),
242  my( std::make_unique<detail::grouped_orders_plugin_impl>(*this) )
243 {
244  // Nothing else to do
245 }
246 
248 
250 {
251  return "grouped_orders";
252 }
253 
255  boost::program_options::options_description& cli,
256  boost::program_options::options_description& cfg
257  )
258 {
259  cli.add_options()
260  ("tracked-groups", boost::program_options::value<string>()->default_value("[10,100]"), // 0.1% and 1%
261  "Group orders by percentage increase on price. Specify a JSON array of numbers here, each number is a group, number 1 means 0.01%. ")
262  ;
263  cfg.add(cli);
264 }
265 
266 void grouped_orders_plugin::plugin_initialize(const boost::program_options::variables_map& options)
267 { try {
268 
269  if( options.count( "tracked-groups" ) > 0 )
270  {
271  const std::string& groups = options["tracked-groups"].as<string>();
272  my->_tracked_groups = fc::json::from_string(groups).as<flat_set<uint16_t>>( 2 );
273  my->_tracked_groups.erase( 0 );
274  }
275  else
276  my->_tracked_groups = fc::json::from_string("[10,100]").as<flat_set<uint16_t>>(2);
277 
279 
281 {
283  detail::limit_order_group_index >( my->_tracked_groups );
284  for( const auto& order : database().get_index_type< limit_order_index >().indices() )
285  groups.object_inserted( order );
286 }
287 
288 const flat_set<uint16_t>& grouped_orders_plugin::tracked_groups() const
289 {
290  return my->_tracked_groups;
291 }
292 
293 const map< limit_order_group_key, limit_order_group_data >& grouped_orders_plugin::limit_order_groups()
294 {
295  const auto& idx = database().get_index_type< limit_order_index >();
296  const auto& pidx = dynamic_cast<const primary_index< limit_order_index >&>(idx);
297  const auto& logidx = pidx.get_secondary_index< detail::limit_order_group_index >();
298  return logidx.get_order_groups();
299 }
300 
301 } }
graphene::db::object_database::add_secondary_index
SecondaryIndexType * add_secondary_index(Args... args)
Definition: object_database.hpp:159
FC_CAPTURE_AND_RETHROW
#define FC_CAPTURE_AND_RETHROW(...)
Definition: exception.hpp:479
graphene::chain::limit_order_object::sell_price
price sell_price
The seller's asking price.
Definition: market_object.hpp:51
graphene::grouped_orders::detail::grouped_orders_plugin_impl::database
graphene::chain::database & database()
Definition: grouped_orders_plugin.cpp:40
graphene::grouped_orders::detail::grouped_orders_plugin_impl
Definition: grouped_orders_plugin.cpp:34
graphene::chain::database
tracks the blockchain state in an extensible manner
Definition: database.hpp:70
wlog
#define wlog(FORMAT,...)
Definition: logger.hpp:123
graphene::protocol::price
The price struct stores asset prices in the BitShares system.
Definition: asset.hpp:108
graphene::grouped_orders::detail::limit_order_group_index::object_modified
virtual void object_modified(const object &after) override
Definition: grouped_orders_plugin.cpp:201
graphene::db::primary_index
Wraps a derived index to intercept calls to create, modify, and remove so that callbacks may be fired...
Definition: index.hpp:312
graphene::app::plugin::database
chain::database & database()
Definition: plugin.hpp:115
graphene::protocol::price::max
static price max(asset_id_type base, asset_id_type quote)
Definition: asset.cpp:106
fc::json::from_string
static variant from_string(const string &utf8_str, parse_type ptype=legacy_parser, uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
Definition: json.cpp:458
graphene::grouped_orders::grouped_orders_plugin::plugin_startup
void plugin_startup() override
Begin normal runtime operations.
Definition: grouped_orders_plugin.cpp:280
graphene::grouped_orders::detail::grouped_orders_plugin_impl::_tracked_groups
flat_set< uint16_t > _tracked_groups
Definition: grouped_orders_plugin.cpp:46
graphene::grouped_orders::limit_order_group_data
Definition: grouped_orders_plugin.hpp:51
graphene::grouped_orders::grouped_orders_plugin::plugin_initialize
void plugin_initialize(const boost::program_options::variables_map &options) override
Perform early startup routines and register plugin indexes, callbacks, etc.
Definition: grouped_orders_plugin.cpp:266
graphene::app::application
Definition: application.hpp:91
graphene::chain::limit_order_object::for_sale
share_type for_sale
The amount for sale, asset id is sell_price.base.asset_id.
Definition: market_object.hpp:50
graphene::grouped_orders::grouped_orders_plugin::~grouped_orders_plugin
~grouped_orders_plugin() override
graphene::grouped_orders::detail::limit_order_group_index::object_removed
virtual void object_removed(const object &obj) override
Definition: grouped_orders_plugin.cpp:189
graphene::protocol::asset::asset_id
asset_id_type asset_id
Definition: asset.hpp:37
fc::variant::as
T as(uint32_t max_depth) const
Definition: variant.hpp:337
graphene::protocol::price::min
static price min(asset_id_type base, asset_id_type quote)
Definition: asset.cpp:109
graphene::grouped_orders::detail::limit_order_group_index::get_tracked_groups
const flat_set< uint16_t > & get_tracked_groups() const
Definition: grouped_orders_plugin.cpp:62
graphene::grouped_orders::grouped_orders_plugin
Definition: grouped_orders_plugin.hpp:69
graphene::grouped_orders::detail::limit_order_group_index::about_to_modify
virtual void about_to_modify(const object &before) override
Definition: grouped_orders_plugin.cpp:195
graphene::grouped_orders::limit_order_group_data::max_price
price max_price
Definition: grouped_orders_plugin.hpp:56
graphene::grouped_orders::detail::limit_order_group_index::limit_order_group_index
limit_order_group_index(const flat_set< uint16_t > &groups)
Definition: grouped_orders_plugin.cpp:55
graphene::grouped_orders::limit_order_group_key
Definition: grouped_orders_plugin.hpp:32
std
Definition: zeroed_array.hpp:76
graphene::grouped_orders::detail::limit_order_group_index
This secondary index is used to track changes on limit order objects.
Definition: grouped_orders_plugin.cpp:52
grouped_orders_plugin.hpp
graphene::protocol::price::base
asset base
Definition: asset.hpp:113
graphene::db::generic_index
Definition: generic_index.hpp:43
graphene::grouped_orders::detail::grouped_orders_plugin_impl::grouped_orders_plugin_impl
grouped_orders_plugin_impl(grouped_orders_plugin &_plugin)
Definition: grouped_orders_plugin.cpp:37
graphene::grouped_orders::grouped_orders_plugin::tracked_groups
const flat_set< uint16_t > & tracked_groups() const
Definition: grouped_orders_plugin.cpp:288
graphene::grouped_orders::grouped_orders_plugin::grouped_orders_plugin
grouped_orders_plugin(graphene::app::application &app)
Definition: grouped_orders_plugin.cpp:240
graphene::db::secondary_index
Definition: index.hpp:139
market_object.hpp
graphene::db::object_database::get_index_type
const IndexType & get_index_type() const
Definition: object_database.hpp:77
graphene::grouped_orders::detail::limit_order_group_index::get_order_groups
const map< limit_order_group_key, limit_order_group_data > & get_order_groups() const
Definition: grouped_orders_plugin.cpp:65
graphene::chain::limit_order_object
an offer to sell an amount of an asset at a specified exchange rate by a certain time
Definition: market_object.hpp:45
graphene::protocol::ratio_type
boost::rational< int32_t > ratio_type
Definition: types.hpp:150
graphene::protocol::price::quote
asset quote
Definition: asset.hpp:114
GRAPHENE_100_PERCENT
#define GRAPHENE_100_PERCENT
Definition: config.hpp:102
graphene
Definition: api.cpp:48
graphene::grouped_orders::detail::grouped_orders_plugin_impl::_self
grouped_orders_plugin & _self
Definition: grouped_orders_plugin.cpp:45
graphene::grouped_orders::grouped_orders_plugin::limit_order_groups
const map< limit_order_group_key, limit_order_group_data > & limit_order_groups()
Definition: grouped_orders_plugin.cpp:293
graphene::grouped_orders::grouped_orders_plugin::plugin_name
std::string plugin_name() const override
Get the name of the plugin.
Definition: grouped_orders_plugin.cpp:249
graphene::grouped_orders::detail::limit_order_group_index::object_inserted
virtual void object_inserted(const object &obj) override
Definition: grouped_orders_plugin.cpp:78
graphene::grouped_orders::grouped_orders_plugin::plugin_set_program_options
void plugin_set_program_options(boost::program_options::options_description &cli, boost::program_options::options_description &cfg) override
Fill in command line parameters used by the plugin.
Definition: grouped_orders_plugin.cpp:254