BitShares-Core  5.0.0
BitShares blockchain implementation and command-line interface 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 ) {}
40 
42  {
43  return _self.database();
44  }
45 
47  flat_set<uint16_t> _tracked_groups;
48 };
49 
54 {
55  public:
56  limit_order_group_index( const flat_set<uint16_t>& groups ) : _tracked_groups( groups ) {};
57 
58  virtual void object_inserted( const object& obj ) override;
59  virtual void object_removed( const object& obj ) override;
60  virtual void about_to_modify( const object& before ) override;
61  virtual void object_modified( const object& after ) override;
62 
63  const flat_set<uint16_t>& get_tracked_groups() const
64  { return _tracked_groups; }
65 
66  const map< limit_order_group_key, limit_order_group_data >& get_order_groups() const
67  { return _og_data; }
68 
69  private:
70  void remove_order( const limit_order_object& obj, bool remove_empty = true );
71 
73  flat_set<uint16_t> _tracked_groups;
74 
76  map< limit_order_group_key, limit_order_group_data > _og_data;
77 };
78 
79 void limit_order_group_index::object_inserted( const object& objct )
80 { try {
81  const limit_order_object& o = static_cast<const limit_order_object&>( objct );
82 
83  auto& idx = _og_data;
84 
85  for( uint16_t group : get_tracked_groups() )
86  {
87  auto create_ogo = [&]() {
89  };
90  // if idx is empty, insert this order
91  // Note: not capped
92  if( idx.empty() )
93  {
94  create_ogo();
95  continue;
96  }
97 
98  // cap the price
99  price capped_price = o.sell_price;
100  price max = o.sell_price.max();
101  price min = o.sell_price.min();
102  bool capped_max = false;
103  bool capped_min = false;
104  if( o.sell_price > max )
105  {
106  capped_price = max;
107  capped_max = true;
108  }
109  else if( o.sell_price < min )
110  {
111  capped_price = min;
112  capped_min = true;
113  }
114  // if idx is not empty, find the group that is next to this order
115  auto itr = idx.lower_bound( limit_order_group_key( group, capped_price ) );
116  bool check_previous = false;
117  if( itr == idx.end() || itr->first.group != group
118  || itr->first.min_price.base.asset_id != o.sell_price.base.asset_id
119  || itr->first.min_price.quote.asset_id != o.sell_price.quote.asset_id )
120  // not same market or group type
121  check_previous = true;
122  else // same market and group type
123  {
124  bool update_max = false;
125  if( capped_price > itr->second.max_price ) // implies itr->min_price <= itr->max_price < max
126  {
127  update_max = true;
128  price max_price = itr->first.min_price * ratio_type( GRAPHENE_100_PERCENT + group, GRAPHENE_100_PERCENT );
129  // max_price should have been capped here
130  if( capped_price > max_price ) // new order is out of range
131  check_previous = true;
132  }
133  if( !check_previous ) // new order is within the range
134  {
135  if( capped_min && o.sell_price < itr->first.min_price )
136  { // need to update itr->min_price here, if itr is below min, and new order is even lower
137  // TODO improve performance
138  limit_order_group_data data( itr->second.max_price, o.for_sale + itr->second.total_for_sale );
139  idx.erase( itr );
140  idx[ limit_order_group_key( group, o.sell_price ) ] = data;
141  }
142  else
143  {
144  if( update_max || ( capped_max && o.sell_price > itr->second.max_price ) )
145  itr->second.max_price = o.sell_price; // store real price here, not capped
146  itr->second.total_for_sale += o.for_sale;
147  }
148  }
149  }
150 
151  if( check_previous )
152  {
153  if( itr == idx.begin() ) // no previous
154  create_ogo();
155  else
156  {
157  --itr; // should be valid
158  if( itr->first.group != group || itr->first.min_price.base.asset_id != o.sell_price.base.asset_id
159  || itr->first.min_price.quote.asset_id != o.sell_price.quote.asset_id )
160  // not same market or group type
161  create_ogo();
162  else // same market and group type
163  {
164  // due to lower_bound, always true: capped_price < itr->first.min_price, so no need to check again,
165  // if new order is in range of itr group, always need to update itr->first.min_price, unless
166  // o.sell_price is higher than max
167  price min_price = itr->second.max_price / ratio_type( GRAPHENE_100_PERCENT + group, GRAPHENE_100_PERCENT );
168  // min_price should have been capped here
169  if( capped_price < min_price ) // new order is out of range
170  create_ogo();
171  else if( capped_max && o.sell_price >= itr->first.min_price )
172  { // itr is above max, and price of new order is even higher
173  if( o.sell_price > itr->second.max_price )
174  itr->second.max_price = o.sell_price;
175  itr->second.total_for_sale += o.for_sale;
176  }
177  else
178  { // new order is within the range
179  // TODO improve performance
180  limit_order_group_data data( itr->second.max_price, o.for_sale + itr->second.total_for_sale );
181  idx.erase( itr );
182  idx[ limit_order_group_key( group, o.sell_price ) ] = data;
183  }
184  }
185  }
186  }
187  }
188 } FC_CAPTURE_AND_RETHROW( (objct) ); }
189 
190 void limit_order_group_index::object_removed( const object& objct )
191 { try {
192  const limit_order_object& o = static_cast<const limit_order_object&>( objct );
193  remove_order( o );
194 } FC_CAPTURE_AND_RETHROW( (objct) ); }
195 
196 void limit_order_group_index::about_to_modify( const object& objct )
197 { try {
198  const limit_order_object& o = static_cast<const limit_order_object&>( objct );
199  remove_order( o, false );
200 } FC_CAPTURE_AND_RETHROW( (objct) ); }
201 
202 void limit_order_group_index::object_modified( const object& objct )
203 { try {
204  object_inserted( objct );
205 } FC_CAPTURE_AND_RETHROW( (objct) ); }
206 
207 void limit_order_group_index::remove_order( const limit_order_object& o, bool remove_empty )
208 {
209  auto& idx = _og_data;
210 
211  for( uint16_t group : get_tracked_groups() )
212  {
213  // find the group that should contain this order
214  auto itr = idx.lower_bound( limit_order_group_key( group, o.sell_price ) );
215  if( itr == idx.end() || itr->first.group != group
216  || itr->first.min_price.base.asset_id != o.sell_price.base.asset_id
217  || itr->first.min_price.quote.asset_id != o.sell_price.quote.asset_id
218  || itr->second.max_price < o.sell_price )
219  {
220  // can not find corresponding group, should not happen
221  wlog( "can not find the order group containing order for removing (price dismatch): ${o}", ("o",o) );
222  continue;
223  }
224  else // found
225  {
226  if( itr->second.total_for_sale < o.for_sale )
227  // should not happen
228  wlog( "can not find the order group containing order for removing (amount dismatch): ${o}", ("o",o) );
229  else if( !remove_empty || itr->second.total_for_sale > o.for_sale )
230  itr->second.total_for_sale -= o.for_sale;
231  else
232  // it's the only order in the group and need to be removed
233  idx.erase( itr );
234  }
235  }
236 }
237 
239 {}
240 
241 } // end namespace detail
242 
243 
245  my( new detail::grouped_orders_plugin_impl(*this) )
246 {
247 }
248 
250 {
251 }
252 
254 {
255  return "grouped_orders";
256 }
257 
259  boost::program_options::options_description& cli,
260  boost::program_options::options_description& cfg
261  )
262 {
263  cli.add_options()
264  ("tracked-groups", boost::program_options::value<string>()->default_value("[10,100]"), // 0.1% and 1%
265  "Group orders by percentage increase on price. Specify a JSON array of numbers here, each number is a group, number 1 means 0.01%. ")
266  ;
267  cfg.add(cli);
268 }
269 
270 void grouped_orders_plugin::plugin_initialize(const boost::program_options::variables_map& options)
271 { try {
272 
273  if( options.count( "tracked-groups" ) )
274  {
275  const std::string& groups = options["tracked-groups"].as<string>();
276  my->_tracked_groups = fc::json::from_string(groups).as<flat_set<uint16_t>>( 2 );
277  my->_tracked_groups.erase( 0 );
278  }
279  else
280  my->_tracked_groups = fc::json::from_string("[10,100]").as<flat_set<uint16_t>>(2);
281 
283 
285 {
287  detail::limit_order_group_index >( my->_tracked_groups );
288  for( const auto& order : database().get_index_type< limit_order_index >().indices() )
289  groups.object_inserted( order );
290 }
291 
292 const flat_set<uint16_t>& grouped_orders_plugin::tracked_groups() const
293 {
294  return my->_tracked_groups;
295 }
296 
297 const map< limit_order_group_key, limit_order_group_data >& grouped_orders_plugin::limit_order_groups()
298 {
299  const auto& idx = database().get_index_type< limit_order_index >();
300  const auto& pidx = dynamic_cast<const primary_index< limit_order_index >&>(idx);
301  const auto& logidx = pidx.get_secondary_index< detail::limit_order_group_index >();
302  return logidx.get_order_groups();
303 }
304 
305 } }
SecondaryIndexType * add_secondary_index(Args...args)
Wraps a derived index to intercept calls to create, modify, and remove so that callbacks may be fired...
Definition: index.hpp:309
T as(uint32_t max_depth) const
Definition: variant.hpp:336
const map< limit_order_group_key, limit_order_group_data > & get_order_groups() const
This secondary index is used to track changes on limit order objects.
boost::rational< int32_t > ratio_type
Definition: types.hpp:133
tracks the blockchain state in an extensible manner
Definition: database.hpp:70
Definition: api.cpp:56
static price max(asset_id_type base, asset_id_type quote)
Definition: asset.cpp:104
virtual void object_modified(const object &after) override
#define wlog(FORMAT,...)
Definition: logger.hpp:123
share_type for_sale
asset id is sell_price.base.asset_id
const flat_set< uint16_t > & tracked_groups() const
static price min(asset_id_type base, asset_id_type quote)
Definition: asset.cpp:105
chain::database & database()
Definition: plugin.hpp:114
The price struct stores asset prices in the BitShares system.
Definition: asset.hpp:114
#define GRAPHENE_100_PERCENT
Definition: config.hpp:102
#define FC_CAPTURE_AND_RETHROW(...)
Definition: exception.hpp:478
virtual void object_inserted(const object &obj) override
virtual void plugin_startup() override
Begin normal runtime operations.
virtual void about_to_modify(const object &before) override
asset_id_type asset_id
Definition: asset.hpp:39
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
virtual void plugin_initialize(const boost::program_options::variables_map &options) override
Perform early startup routines and register plugin indexes, callbacks, etc.
const map< limit_order_group_key, limit_order_group_data > & limit_order_groups()
virtual 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.
const IndexType & get_index_type() const
an offer to sell a amount of a asset at a specified exchange rate by a certain timeThis limit_order_o...