2018-10-09 14:33:31 +00:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2018 Peerplays Blockchain Standards Association, and contributors.
|
|
|
|
|
*
|
|
|
|
|
* The MIT License
|
|
|
|
|
*
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
|
*
|
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
|
*
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
|
* THE SOFTWARE.
|
|
|
|
|
*/
|
2017-07-27 23:35:13 +00:00
|
|
|
#include <fc/filesystem.hpp>
|
|
|
|
|
#include <fc/optional.hpp>
|
|
|
|
|
#include <fc/variant_object.hpp>
|
|
|
|
|
|
|
|
|
|
#include <graphene/app/application.hpp>
|
|
|
|
|
|
|
|
|
|
#include <graphene/chain/block_database.hpp>
|
|
|
|
|
#include <graphene/chain/database.hpp>
|
|
|
|
|
#include <graphene/chain/betting_market_object.hpp>
|
|
|
|
|
|
|
|
|
|
#include <graphene/chain/witness_object.hpp>
|
|
|
|
|
|
|
|
|
|
#include <graphene/utilities/key_conversion.hpp>
|
|
|
|
|
|
|
|
|
|
#include <graphene/bookie/bookie_api.hpp>
|
|
|
|
|
#include <graphene/bookie/bookie_plugin.hpp>
|
2017-08-09 21:17:51 +00:00
|
|
|
#include <graphene/bookie/bookie_objects.hpp>
|
2017-07-27 23:35:13 +00:00
|
|
|
|
|
|
|
|
namespace graphene { namespace bookie {
|
|
|
|
|
|
|
|
|
|
namespace detail {
|
|
|
|
|
|
|
|
|
|
class bookie_api_impl
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
bookie_api_impl(graphene::app::application& _app);
|
|
|
|
|
|
|
|
|
|
binned_order_book get_binned_order_book(graphene::chain::betting_market_id_type betting_market_id, int32_t precision);
|
|
|
|
|
std::shared_ptr<graphene::bookie::bookie_plugin> get_plugin();
|
2017-07-28 16:23:57 +00:00
|
|
|
asset get_total_matched_bet_amount_for_betting_market_group(betting_market_group_id_type group_id);
|
2017-08-09 21:17:51 +00:00
|
|
|
std::vector<event_object> get_events_containing_sub_string(const std::string& sub_string, const std::string& language);
|
|
|
|
|
fc::variants get_objects(const vector<object_id_type>& ids) const;
|
2017-09-02 23:03:36 +00:00
|
|
|
std::vector<matched_bet_object> get_matched_bets_for_bettor(account_id_type bettor_id) const;
|
2017-09-06 20:52:58 +00:00
|
|
|
std::vector<matched_bet_object> get_all_matched_bets_for_bettor(account_id_type bettor_id, bet_id_type start, unsigned limit) const;
|
2017-07-27 23:35:13 +00:00
|
|
|
graphene::app::application& app;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bookie_api_impl::bookie_api_impl(graphene::app::application& _app) : app(_app)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
binned_order_book bookie_api_impl::get_binned_order_book(graphene::chain::betting_market_id_type betting_market_id, int32_t precision)
|
|
|
|
|
{
|
|
|
|
|
std::shared_ptr<graphene::chain::database> db = app.chain_database();
|
|
|
|
|
const auto& bet_odds_idx = db->get_index_type<graphene::chain::bet_object_index>().indices().get<graphene::chain::by_odds>();
|
2017-08-01 19:40:49 +00:00
|
|
|
const chain_parameters& current_params = db->get_global_properties().parameters;
|
2017-07-27 23:35:13 +00:00
|
|
|
|
|
|
|
|
graphene::chain::bet_multiplier_type bin_size = GRAPHENE_BETTING_ODDS_PRECISION;
|
|
|
|
|
if (precision > 0)
|
|
|
|
|
for (int32_t i = 0; i < precision; ++i) {
|
|
|
|
|
FC_ASSERT(bin_size > (GRAPHENE_BETTING_MIN_MULTIPLIER - GRAPHENE_BETTING_ODDS_PRECISION), "invalid precision");
|
|
|
|
|
bin_size /= 10;
|
|
|
|
|
}
|
|
|
|
|
else if (precision < 0)
|
|
|
|
|
for (int32_t i = 0; i > precision; --i) {
|
|
|
|
|
FC_ASSERT(bin_size < (GRAPHENE_BETTING_MAX_MULTIPLIER - GRAPHENE_BETTING_ODDS_PRECISION), "invalid precision");
|
|
|
|
|
bin_size *= 10;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
binned_order_book result;
|
|
|
|
|
|
|
|
|
|
// use a bet_object here for convenience. we really only use it to track the amount, odds, and back_or_lay
|
|
|
|
|
fc::optional<bet_object> current_bin;
|
|
|
|
|
|
|
|
|
|
auto flush_current_bin = [¤t_bin, &result]()
|
|
|
|
|
{
|
|
|
|
|
if (current_bin) // do nothing if the current bin is empty
|
|
|
|
|
{
|
|
|
|
|
order_bin current_order_bin;
|
|
|
|
|
|
|
|
|
|
current_order_bin.backer_multiplier = current_bin->backer_multiplier;
|
2018-05-03 22:54:04 +00:00
|
|
|
current_order_bin.amount_to_bet = current_bin->amount_to_bet.amount;
|
2017-08-01 19:40:49 +00:00
|
|
|
//idump((*current_bin)(current_order_bin));
|
2018-05-03 22:54:04 +00:00
|
|
|
if (current_bin->back_or_lay == bet_type::back)
|
2017-07-27 23:35:13 +00:00
|
|
|
result.aggregated_back_bets.emplace_back(std::move(current_order_bin));
|
|
|
|
|
else // current_bin is aggregating back positions
|
|
|
|
|
result.aggregated_lay_bets.emplace_back(std::move(current_order_bin));
|
|
|
|
|
|
|
|
|
|
current_bin.reset();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-08-09 15:12:59 +00:00
|
|
|
// iterate through both sides of the order book (backs at increasing odds then lays at decreasing odds)
|
2017-07-27 23:35:13 +00:00
|
|
|
for (auto bet_odds_iter = bet_odds_idx.lower_bound(std::make_tuple(betting_market_id));
|
|
|
|
|
bet_odds_iter != bet_odds_idx.end() && betting_market_id == bet_odds_iter->betting_market_id;
|
|
|
|
|
++bet_odds_iter)
|
|
|
|
|
{
|
|
|
|
|
if (current_bin &&
|
2018-05-03 22:54:04 +00:00
|
|
|
(bet_odds_iter->back_or_lay != current_bin->back_or_lay /* we have switched from back to lay bets */ ||
|
2017-08-09 15:12:59 +00:00
|
|
|
(bet_odds_iter->back_or_lay == bet_type::back ? bet_odds_iter->backer_multiplier > current_bin->backer_multiplier :
|
|
|
|
|
bet_odds_iter->backer_multiplier < current_bin->backer_multiplier)))
|
2017-07-27 23:35:13 +00:00
|
|
|
flush_current_bin();
|
|
|
|
|
|
|
|
|
|
if (!current_bin)
|
|
|
|
|
{
|
|
|
|
|
// if there is no current bin, create one appropriate for the bet we're processing
|
|
|
|
|
current_bin = graphene::chain::bet_object();
|
2017-08-01 19:40:49 +00:00
|
|
|
|
2017-08-09 15:12:59 +00:00
|
|
|
// for back bets, we want to group all bets with odds from 3.0001 to 4 into the "4" bin
|
|
|
|
|
// for lay bets, we want to group all bets with odds from 3 to 3.9999 into the "3" bin
|
|
|
|
|
if (bet_odds_iter->back_or_lay == bet_type::back)
|
|
|
|
|
{
|
|
|
|
|
current_bin->backer_multiplier = (bet_odds_iter->backer_multiplier + bin_size - 1) / bin_size * bin_size;
|
2018-09-05 14:43:35 +00:00
|
|
|
current_bin->backer_multiplier = std::min<graphene::chain::bet_multiplier_type>(current_bin->backer_multiplier, current_params.max_bet_multiplier());
|
2018-05-03 22:54:04 +00:00
|
|
|
current_bin->back_or_lay = bet_type::back;
|
2017-08-09 15:12:59 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
current_bin->backer_multiplier = bet_odds_iter->backer_multiplier / bin_size * bin_size;
|
2018-09-05 14:43:35 +00:00
|
|
|
current_bin->backer_multiplier = std::max<graphene::chain::bet_multiplier_type>(current_bin->backer_multiplier, current_params.min_bet_multiplier());
|
2018-05-03 22:54:04 +00:00
|
|
|
current_bin->back_or_lay = bet_type::lay;
|
2017-08-09 15:12:59 +00:00
|
|
|
}
|
|
|
|
|
|
2017-08-01 19:40:49 +00:00
|
|
|
current_bin->amount_to_bet.amount = 0;
|
2017-07-27 23:35:13 +00:00
|
|
|
}
|
|
|
|
|
|
2018-05-03 22:54:04 +00:00
|
|
|
current_bin->amount_to_bet.amount += bet_odds_iter->amount_to_bet.amount;
|
2017-07-27 23:35:13 +00:00
|
|
|
}
|
|
|
|
|
if (current_bin)
|
|
|
|
|
flush_current_bin();
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-09 21:17:51 +00:00
|
|
|
fc::variants bookie_api_impl::get_objects(const vector<object_id_type>& ids) const
|
|
|
|
|
{
|
|
|
|
|
std::shared_ptr<graphene::chain::database> db = app.chain_database();
|
|
|
|
|
fc::variants result;
|
|
|
|
|
result.reserve(ids.size());
|
|
|
|
|
|
|
|
|
|
std::transform(ids.begin(), ids.end(), std::back_inserter(result),
|
|
|
|
|
[this, &db](object_id_type id) -> fc::variant {
|
2017-08-10 19:44:07 +00:00
|
|
|
switch (id.type())
|
2017-08-09 21:17:51 +00:00
|
|
|
{
|
2017-08-10 19:44:07 +00:00
|
|
|
case event_id_type::type_id:
|
|
|
|
|
{
|
|
|
|
|
auto& persistent_events_by_event_id = db->get_index_type<detail::persistent_event_index>().indices().get<by_event_id>();
|
|
|
|
|
auto iter = persistent_events_by_event_id.find(id.as<event_id_type>());
|
|
|
|
|
if (iter != persistent_events_by_event_id.end())
|
|
|
|
|
return iter->ephemeral_event_object.to_variant();
|
|
|
|
|
else
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
case bet_id_type::type_id:
|
|
|
|
|
{
|
|
|
|
|
auto& persistent_bets_by_bet_id = db->get_index_type<detail::persistent_bet_index>().indices().get<by_bet_id>();
|
|
|
|
|
auto iter = persistent_bets_by_bet_id.find(id.as<bet_id_type>());
|
|
|
|
|
if (iter != persistent_bets_by_bet_id.end())
|
|
|
|
|
return iter->ephemeral_bet_object.to_variant();
|
|
|
|
|
else
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
case betting_market_object::type_id:
|
|
|
|
|
{
|
|
|
|
|
auto& persistent_betting_markets_by_betting_market_id = db->get_index_type<detail::persistent_betting_market_index>().indices().get<by_betting_market_id>();
|
|
|
|
|
auto iter = persistent_betting_markets_by_betting_market_id.find(id.as<betting_market_id_type>());
|
|
|
|
|
if (iter != persistent_betting_markets_by_betting_market_id.end())
|
|
|
|
|
return iter->ephemeral_betting_market_object.to_variant();
|
|
|
|
|
else
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
case betting_market_group_object::type_id:
|
|
|
|
|
{
|
|
|
|
|
auto& persistent_betting_market_groups_by_betting_market_group_id = db->get_index_type<detail::persistent_betting_market_group_index>().indices().get<by_betting_market_group_id>();
|
|
|
|
|
auto iter = persistent_betting_market_groups_by_betting_market_group_id.find(id.as<betting_market_group_id_type>());
|
|
|
|
|
if (iter != persistent_betting_market_groups_by_betting_market_group_id.end())
|
|
|
|
|
return iter->ephemeral_betting_market_group_object.to_variant();
|
|
|
|
|
else
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return {};
|
2017-08-09 21:17:51 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-02 23:03:36 +00:00
|
|
|
std::vector<matched_bet_object> bookie_api_impl::get_matched_bets_for_bettor(account_id_type bettor_id) const
|
|
|
|
|
{
|
|
|
|
|
std::vector<matched_bet_object> result;
|
|
|
|
|
std::shared_ptr<graphene::chain::database> db = app.chain_database();
|
|
|
|
|
auto& persistent_bets_by_bettor_id = db->get_index_type<detail::persistent_bet_index>().indices().get<by_bettor_id>();
|
|
|
|
|
auto iter = persistent_bets_by_bettor_id.lower_bound(std::make_tuple(bettor_id, true));
|
|
|
|
|
while (iter != persistent_bets_by_bettor_id.end() &&
|
|
|
|
|
iter->get_bettor_id() == bettor_id &&
|
|
|
|
|
iter->is_matched())
|
|
|
|
|
{
|
|
|
|
|
matched_bet_object match;
|
|
|
|
|
match.id = iter->ephemeral_bet_object.id;
|
|
|
|
|
match.bettor_id = iter->ephemeral_bet_object.bettor_id;
|
|
|
|
|
match.betting_market_id = iter->ephemeral_bet_object.betting_market_id;
|
|
|
|
|
match.amount_to_bet = iter->ephemeral_bet_object.amount_to_bet;
|
|
|
|
|
match.back_or_lay = iter->ephemeral_bet_object.back_or_lay;
|
|
|
|
|
match.end_of_delay = iter->ephemeral_bet_object.end_of_delay;
|
|
|
|
|
match.amount_matched = iter->amount_matched;
|
2017-09-06 20:52:58 +00:00
|
|
|
match.associated_operations = iter->associated_operations;
|
2017-09-02 23:03:36 +00:00
|
|
|
result.emplace_back(std::move(match));
|
|
|
|
|
|
|
|
|
|
++iter;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-06 20:52:58 +00:00
|
|
|
std::vector<matched_bet_object> bookie_api_impl::get_all_matched_bets_for_bettor(account_id_type bettor_id, bet_id_type start, unsigned limit) const
|
|
|
|
|
{
|
|
|
|
|
FC_ASSERT(limit <= 1000, "You may request at most 1000 matched bets at a time");
|
|
|
|
|
|
|
|
|
|
std::vector<matched_bet_object> result;
|
|
|
|
|
std::shared_ptr<graphene::chain::database> db = app.chain_database();
|
|
|
|
|
auto& persistent_bets_by_bettor_id = db->get_index_type<detail::persistent_bet_index>().indices().get<by_bettor_id>();
|
|
|
|
|
persistent_bet_multi_index_type::index<by_bettor_id>::type::iterator iter;
|
|
|
|
|
if (start == bet_id_type())
|
|
|
|
|
iter = persistent_bets_by_bettor_id.lower_bound(std::make_tuple(bettor_id, true));
|
|
|
|
|
else
|
|
|
|
|
iter = persistent_bets_by_bettor_id.lower_bound(std::make_tuple(bettor_id, true, start));
|
|
|
|
|
while (iter != persistent_bets_by_bettor_id.end() &&
|
|
|
|
|
iter->get_bettor_id() == bettor_id &&
|
|
|
|
|
iter->is_matched() &&
|
|
|
|
|
result.size() < limit)
|
|
|
|
|
{
|
|
|
|
|
matched_bet_object match;
|
|
|
|
|
match.id = iter->ephemeral_bet_object.id;
|
|
|
|
|
match.bettor_id = iter->ephemeral_bet_object.bettor_id;
|
|
|
|
|
match.betting_market_id = iter->ephemeral_bet_object.betting_market_id;
|
|
|
|
|
match.amount_to_bet = iter->ephemeral_bet_object.amount_to_bet;
|
|
|
|
|
match.back_or_lay = iter->ephemeral_bet_object.back_or_lay;
|
|
|
|
|
match.end_of_delay = iter->ephemeral_bet_object.end_of_delay;
|
|
|
|
|
match.amount_matched = iter->amount_matched;
|
|
|
|
|
result.emplace_back(std::move(match));
|
|
|
|
|
|
|
|
|
|
++iter;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2017-09-02 23:03:36 +00:00
|
|
|
|
2017-07-27 23:35:13 +00:00
|
|
|
std::shared_ptr<graphene::bookie::bookie_plugin> bookie_api_impl::get_plugin()
|
|
|
|
|
{
|
|
|
|
|
return app.get_plugin<graphene::bookie::bookie_plugin>("bookie");
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-28 16:23:57 +00:00
|
|
|
asset bookie_api_impl::get_total_matched_bet_amount_for_betting_market_group(betting_market_group_id_type group_id)
|
|
|
|
|
{
|
|
|
|
|
return get_plugin()->get_total_matched_bet_amount_for_betting_market_group(group_id);
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-09 21:17:51 +00:00
|
|
|
std::vector<event_object> bookie_api_impl::get_events_containing_sub_string(const std::string& sub_string, const std::string& language)
|
2017-07-31 12:26:20 +00:00
|
|
|
{
|
2017-08-09 21:17:51 +00:00
|
|
|
return get_plugin()->get_events_containing_sub_string(sub_string, language);
|
2017-07-31 12:26:20 +00:00
|
|
|
}
|
|
|
|
|
|
2017-07-27 23:35:13 +00:00
|
|
|
} // detail
|
|
|
|
|
|
|
|
|
|
bookie_api::bookie_api(graphene::app::application& app) :
|
|
|
|
|
my(std::make_shared<detail::bookie_api_impl>(app))
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
binned_order_book bookie_api::get_binned_order_book(graphene::chain::betting_market_id_type betting_market_id, int32_t precision)
|
|
|
|
|
{
|
|
|
|
|
return my->get_binned_order_book(betting_market_id, precision);
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-28 16:23:57 +00:00
|
|
|
asset bookie_api::get_total_matched_bet_amount_for_betting_market_group(betting_market_group_id_type group_id)
|
|
|
|
|
{
|
|
|
|
|
return my->get_total_matched_bet_amount_for_betting_market_group(group_id);
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-31 12:26:20 +00:00
|
|
|
std::vector<event_object> bookie_api::get_events_containing_sub_string(const std::string& sub_string, const std::string& language)
|
|
|
|
|
{
|
2017-08-09 21:17:51 +00:00
|
|
|
return my->get_events_containing_sub_string(sub_string, language);
|
2017-07-31 12:26:20 +00:00
|
|
|
}
|
|
|
|
|
|
2017-08-09 21:17:51 +00:00
|
|
|
fc::variants bookie_api::get_objects(const vector<object_id_type>& ids) const
|
|
|
|
|
{
|
|
|
|
|
return my->get_objects(ids);
|
|
|
|
|
}
|
2017-07-31 12:26:20 +00:00
|
|
|
|
2017-09-02 23:03:36 +00:00
|
|
|
std::vector<matched_bet_object> bookie_api::get_matched_bets_for_bettor(account_id_type bettor_id) const
|
|
|
|
|
{
|
|
|
|
|
return my->get_matched_bets_for_bettor(bettor_id);
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-06 20:52:58 +00:00
|
|
|
std::vector<matched_bet_object> bookie_api::get_all_matched_bets_for_bettor(account_id_type bettor_id,
|
|
|
|
|
bet_id_type start /* = bet_id_type() */,
|
|
|
|
|
unsigned limit /* = 1000 */) const
|
|
|
|
|
{
|
|
|
|
|
return my->get_all_matched_bets_for_bettor(bettor_id, start, limit);
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-27 23:35:13 +00:00
|
|
|
} } // graphene::bookie
|
|
|
|
|
|
2017-07-28 16:23:57 +00:00
|
|
|
|