Change the rounding used when matching bets to never round, bets are always matched at

exactly the maker's odds.
This commit is contained in:
Eric Frias 2017-08-01 15:40:49 -04:00
parent 85b26c6905
commit 93088a204d
6 changed files with 229 additions and 39 deletions

View file

@ -274,7 +274,7 @@ void_result bet_place_evaluator::do_evaluate(const bet_place_operation& op)
} }
// is it possible to match this bet // is it possible to match this bet
FC_ASSERT(bet_object::get_matching_amount(op.amount_to_bet.amount, op.backer_multiplier, op.back_or_lay) != 0, FC_ASSERT(bet_object::get_exact_matching_amount(op.amount_to_bet.amount, op.backer_multiplier, op.back_or_lay) != 0,
"Bet cannot be matched"); "Bet cannot be matched");
#if 0 #if 0

View file

@ -1,29 +1,74 @@
#include <graphene/chain/betting_market_object.hpp> #include <graphene/chain/betting_market_object.hpp>
#include <boost/math/common_factor_rt.hpp>
namespace graphene { namespace chain { namespace graphene { namespace chain {
/* static */ share_type bet_object::get_matching_amount(share_type bet_amount, bet_multiplier_type backer_multiplier, bet_type back_or_lay) /* static */ share_type bet_object::get_approximate_matching_amount(share_type bet_amount, bet_multiplier_type backer_multiplier, bet_type back_or_lay, bool round_up /* = false */)
{ {
fc::uint128_t amount_to_match_128 = bet_amount.value; fc::uint128_t amount_to_match_128 = bet_amount.value;
if (back_or_lay == bet_type::back) if (back_or_lay == bet_type::back)
{ {
amount_to_match_128 *= backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION; amount_to_match_128 *= backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION;
if (round_up)
amount_to_match_128 += GRAPHENE_BETTING_ODDS_PRECISION - 1;
amount_to_match_128 /= GRAPHENE_BETTING_ODDS_PRECISION; amount_to_match_128 /= GRAPHENE_BETTING_ODDS_PRECISION;
} }
else else
{ {
amount_to_match_128 *= GRAPHENE_BETTING_ODDS_PRECISION; amount_to_match_128 *= GRAPHENE_BETTING_ODDS_PRECISION;
if (round_up)
amount_to_match_128 += backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION - 1;
amount_to_match_128 /= backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION; amount_to_match_128 /= backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION;
} }
return amount_to_match_128.to_uint64(); return amount_to_match_128.to_uint64();
} }
share_type bet_object::get_matching_amount() const share_type bet_object::get_approximate_matching_amount(bool round_up /* = false */) const
{ {
return get_matching_amount(amount_to_bet.amount, backer_multiplier, back_or_lay); return get_approximate_matching_amount(amount_to_bet.amount, backer_multiplier, back_or_lay, round_up);
} }
/* static */ share_type bet_object::get_exact_matching_amount(share_type bet_amount, bet_multiplier_type backer_multiplier, bet_type back_or_lay)
{
share_type back_ratio;
share_type lay_ratio;
std::tie(back_ratio, lay_ratio) = get_ratio(backer_multiplier);
if (back_or_lay == bet_type::back)
return bet_amount / back_ratio * lay_ratio;
else
return bet_amount / lay_ratio * back_ratio;
}
share_type bet_object::get_exact_matching_amount() const
{
return get_exact_matching_amount(amount_to_bet.amount, backer_multiplier, back_or_lay);
}
/* static */ std::pair<share_type, share_type> bet_object::get_ratio(bet_multiplier_type backer_multiplier)
{
share_type gcd = boost::math::gcd<bet_multiplier_type>(GRAPHENE_BETTING_ODDS_PRECISION, backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION);
return std::make_pair(GRAPHENE_BETTING_ODDS_PRECISION / gcd, (backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION) / gcd);
}
std::pair<share_type, share_type> bet_object::get_ratio() const
{
return get_ratio(backer_multiplier);
}
share_type bet_object::get_minimum_matchable_amount() const
{
share_type gcd = boost::math::gcd<bet_multiplier_type>(GRAPHENE_BETTING_ODDS_PRECISION, backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION);
return (back_or_lay == bet_type::back ? GRAPHENE_BETTING_ODDS_PRECISION : backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION) / gcd;
}
share_type bet_object::get_minimum_matching_amount() const
{
share_type gcd = boost::math::gcd<bet_multiplier_type>(GRAPHENE_BETTING_ODDS_PRECISION, backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION);
return (back_or_lay == bet_type::lay ? GRAPHENE_BETTING_ODDS_PRECISION : backer_multiplier - GRAPHENE_BETTING_ODDS_PRECISION) / gcd;
}
share_type betting_market_position_object::reduce() share_type betting_market_position_object::reduce()
{ {
share_type additional_not_cancel_balance = std::min(pay_if_payout_condition, pay_if_not_payout_condition); share_type additional_not_cancel_balance = std::min(pay_if_payout_condition, pay_if_not_payout_condition);

View file

@ -16,9 +16,13 @@ void database::cancel_bet( const bet_object& bet, bool create_virtual_op )
adjust_balance(bet.bettor_id, amount_to_refund); //return unmatched stake + fees adjust_balance(bet.bettor_id, amount_to_refund); //return unmatched stake + fees
//TODO: do special fee accounting as required //TODO: do special fee accounting as required
if (create_virtual_op) if (create_virtual_op)
push_applied_operation(bet_canceled_operation(bet.bettor_id, bet.id, {
bet.amount_to_bet, bet_canceled_operation bet_canceled_virtual_op(bet.bettor_id, bet.id,
bet.amount_reserved_for_fees)); bet.amount_to_bet,
bet.amount_reserved_for_fees);
//idump((bet_canceled_virtual_op));
push_applied_operation(std::move(bet_canceled_virtual_op));
}
remove(bet); remove(bet);
} }
@ -55,7 +59,7 @@ void database::validate_betting_market_group_resolutions(const betting_market_gr
{ {
const betting_market_object& betting_market = *betting_market_itr; const betting_market_object& betting_market = *betting_market_itr;
// every betting market in the group tied with resolution // every betting market in the group tied with resolution
idump((betting_market.id)(resolutions)); //idump((betting_market.id)(resolutions));
assert(resolutions.count(betting_market.id)); assert(resolutions.count(betting_market.id));
++betting_market_itr; ++betting_market_itr;
} }
@ -181,7 +185,7 @@ void database::resolve_betting_market_group(const betting_market_group_object& b
// pay winning - rake // pay winning - rake
adjust_balance(bettor_id, asset(payout_amounts - rake_amount, betting_market_group.asset_id)); adjust_balance(bettor_id, asset(payout_amounts - rake_amount, betting_market_group.asset_id));
// [ROL] // [ROL]
idump((payout_amounts)(net_profits.value)(rake_amount.value)); //idump((payout_amounts)(net_profits.value)(rake_amount.value));
push_applied_operation(betting_market_group_resolved_operation(bettor_id, push_applied_operation(betting_market_group_resolved_operation(bettor_id,
betting_market_group.id, betting_market_group.id,
@ -212,14 +216,17 @@ void database::get_required_deposit_for_bet(const betting_market_object& betting
bool maybe_cull_small_bet( database& db, const bet_object& bet_object_to_cull ) bool maybe_cull_small_bet( database& db, const bet_object& bet_object_to_cull )
{ {
/** /**
* There are times when this bet can't be matched (for example, it's now laying a 2:1 bet for * There are times when this bet can't be even partially matched at its stated odds
* 1 satoshi, so it could only be matched by half a satoshi). Remove these bets from * For example, say it's a back bet for 20 satoshis at 1.92 odds (which comes out to 25:23
* the books. * odds). It's not possible for it to match at exact odds since it's < 25
*
* Remove these bets from the books to reduce clutter.
*/ */
share_type minimum_matchable_amount = bet_object_to_cull.get_minimum_matchable_amount();
if( bet_object_to_cull.get_matching_amount() == 0 ) if (bet_object_to_cull.amount_to_bet.amount < minimum_matchable_amount)
{ {
ilog("applied epsilon logic"); dlog("culling small bet of ${amount}, smaller than the minimum ${minimum_matchable_amount} at odds ${odds}",
("amount", bet_object_to_cull.amount_to_bet.amount)("minimum_matchable_amount", minimum_matchable_amount)("odds", bet_object_to_cull.backer_multiplier));
db.cancel_bet(bet_object_to_cull); db.cancel_bet(bet_object_to_cull);
return true; return true;
} }
@ -284,11 +291,14 @@ bool bet_was_matched(database& db, const bet_object& bet,
// generate a virtual "match" op // generate a virtual "match" op
asset asset_amount_bet(amount_bet, bet.amount_to_bet.asset_id); asset asset_amount_bet(amount_bet, bet.amount_to_bet.asset_id);
db.push_applied_operation(bet_matched_operation(bet.bettor_id, bet.id, bet.betting_market_id,
asset_amount_bet, bet_matched_operation bet_matched_virtual_op(bet.bettor_id, bet.id, bet.betting_market_id,
fee_paid, asset_amount_bet,
actual_multiplier, fee_paid,
guaranteed_winnings_returned)); actual_multiplier,
guaranteed_winnings_returned);
//idump((bet_matched_virtual_op));
db.push_applied_operation(std::move(bet_matched_virtual_op));
// update the bet on the books // update the bet on the books
if (asset_amount_bet == bet.amount_to_bet) if (asset_amount_bet == bet.amount_to_bet)
@ -328,23 +338,51 @@ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker
assert(taker_bet.back_or_lay != maker_bet.back_or_lay); assert(taker_bet.back_or_lay != maker_bet.back_or_lay);
int result = 0; int result = 0;
share_type maximum_amount_to_match = taker_bet.get_matching_amount(); //idump((taker_bet)(maker_bet));
if (maximum_amount_to_match <= maker_bet.amount_to_bet.amount) // using the maker's odds, figure out how much of the maker's bet we would match, rounding down
{ // go ahead and get look up the ratio for the bet (a bet with odds 1.92 will have a ratio 25:23)
// we will consume the entire taker bet share_type back_odds_ratio;
result |= bet_was_matched(db, taker_bet, taker_bet.amount_to_bet.amount, maximum_amount_to_match, maker_bet.backer_multiplier, true); share_type lay_odds_ratio;
result |= bet_was_matched(db, maker_bet, maximum_amount_to_match, taker_bet.amount_to_bet.amount, maker_bet.backer_multiplier, true) << 1; std::tie(back_odds_ratio, lay_odds_ratio) = maker_bet.get_ratio();
}
else
{
// we will consume the entire maker bet. Figure out how much of the taker bet we can fill.
share_type taker_amount = maker_bet.get_matching_amount();
share_type maker_amount = bet_object::get_matching_amount(taker_amount, maker_bet.backer_multiplier, taker_bet.back_or_lay);
result |= bet_was_matched(db, taker_bet, taker_amount, maker_amount, maker_bet.backer_multiplier, true); // and make some shortcuts to get to the maker's and taker's side of the ratio
result |= bet_was_matched(db, maker_bet, maker_amount, taker_amount, maker_bet.backer_multiplier, true) << 1; const share_type& maker_odds_ratio = maker_bet.back_or_lay == bet_type::back ? back_odds_ratio : lay_odds_ratio;
const share_type& taker_odds_ratio = maker_bet.back_or_lay == bet_type::back ? lay_odds_ratio : back_odds_ratio;
//idump((back_odds_ratio)(lay_odds_ratio));
//idump((maker_odds_ratio)(taker_odds_ratio));
// now figure out how much of the maker bet we'll consume. We don't yet know whether the maker or taker
// will be the limiting factor.
share_type maximum_taker_factor = taker_bet.amount_to_bet.amount / taker_odds_ratio;
share_type maximum_maker_factor = maker_bet.amount_to_bet.amount / maker_odds_ratio;
share_type maximum_factor = std::min(maximum_taker_factor, maximum_maker_factor);
share_type maker_amount_to_match = maximum_factor * maker_odds_ratio;
share_type taker_amount_to_match = maximum_factor * taker_odds_ratio;
//idump((maker_amount_to_match)(taker_amount_to_match));
// TODO: analyze whether maximum_maker_amount_to_match can ever be zero here
assert(maker_amount_to_match != 0);
if (maker_amount_to_match == 0)
return 0;
#ifndef NDEBUG
assert(taker_amount_to_match <= taker_bet.amount_to_bet.amount);
assert(taker_amount_to_match / taker_odds_ratio * taker_odds_ratio == taker_amount_to_match);
{
// verify we're getting the odds we expect
fc::uint128_t payout_128 = maker_amount_to_match.value;
payout_128 += taker_amount_to_match.value;
payout_128 *= GRAPHENE_BETTING_ODDS_PRECISION;
payout_128 /= maker_bet.back_or_lay == bet_type::back ? maker_amount_to_match.value : taker_amount_to_match.value;
assert(payout_128.to_uint64() == maker_bet.backer_multiplier);
} }
#endif
//idump((taker_amount_to_match)(maker_amount_to_match));
result |= bet_was_matched(db, taker_bet, taker_amount_to_match, maker_amount_to_match, maker_bet.backer_multiplier, true);
result |= bet_was_matched(db, maker_bet, maker_amount_to_match, taker_amount_to_match, maker_bet.backer_multiplier, true) << 1;
assert(result != 0); assert(result != 0);
return result; return result;

View file

@ -98,8 +98,21 @@ class bet_object : public graphene::db::abstract_object< bet_object >
bet_type back_or_lay; bet_type back_or_lay;
static share_type get_matching_amount(share_type bet_amount, bet_multiplier_type backer_multiplier, bet_type back_or_lay); static share_type get_approximate_matching_amount(share_type bet_amount, bet_multiplier_type backer_multiplier, bet_type back_or_lay, bool round_up = false);
share_type get_matching_amount() const;
// returns the amount of a bet that completely matches this bet
share_type get_approximate_matching_amount(bool round_up = false) const;
static share_type get_exact_matching_amount(share_type bet_amount, bet_multiplier_type backer_multiplier, bet_type back_or_lay);
share_type get_exact_matching_amount() const;
static std::pair<share_type, share_type> get_ratio(bet_multiplier_type backer_multiplier);
std::pair<share_type, share_type> get_ratio() const;
// returns the minimum amount this bet could have that could be matched at these odds
share_type get_minimum_matchable_amount() const;
// returns the minimum amount another user could bet to match this bet at these odds
share_type get_minimum_matching_amount() const;
}; };
class betting_market_position_object : public graphene::db::abstract_object< betting_market_position_object > class betting_market_position_object : public graphene::db::abstract_object< betting_market_position_object >

View file

@ -41,6 +41,7 @@ binned_order_book bookie_api_impl::get_binned_order_book(graphene::chain::bettin
{ {
std::shared_ptr<graphene::chain::database> db = app.chain_database(); 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>(); const auto& bet_odds_idx = db->get_index_type<graphene::chain::bet_object_index>().indices().get<graphene::chain::by_odds>();
const chain_parameters& current_params = db->get_global_properties().parameters;
graphene::chain::bet_multiplier_type bin_size = GRAPHENE_BETTING_ODDS_PRECISION; graphene::chain::bet_multiplier_type bin_size = GRAPHENE_BETTING_ODDS_PRECISION;
if (precision > 0) if (precision > 0)
@ -68,7 +69,8 @@ binned_order_book bookie_api_impl::get_binned_order_book(graphene::chain::bettin
order_bin current_order_bin; order_bin current_order_bin;
current_order_bin.backer_multiplier = current_bin->backer_multiplier; current_order_bin.backer_multiplier = current_bin->backer_multiplier;
current_order_bin.amount_to_bet = current_bin->get_matching_amount();; current_order_bin.amount_to_bet = current_bin->get_approximate_matching_amount(true /* round up */);
//idump((*current_bin)(current_order_bin));
if (current_bin->back_or_lay == bet_type::lay) if (current_bin->back_or_lay == bet_type::lay)
result.aggregated_back_bets.emplace_back(std::move(current_order_bin)); result.aggregated_back_bets.emplace_back(std::move(current_order_bin));
else // current_bin is aggregating back positions else // current_bin is aggregating back positions
@ -91,12 +93,14 @@ binned_order_book bookie_api_impl::get_binned_order_book(graphene::chain::bettin
{ {
// if there is no current bin, create one appropriate for the bet we're processing // if there is no current bin, create one appropriate for the bet we're processing
current_bin = graphene::chain::bet_object(); current_bin = graphene::chain::bet_object();
current_bin->backer_multiplier = (bet_odds_iter->backer_multiplier + bin_size - 1) / bin_size * bin_size; current_bin->backer_multiplier = (bet_odds_iter->backer_multiplier + bin_size - 1) / bin_size * bin_size;
current_bin->amount_to_bet.amount = 0; current_bin->backer_multiplier = std::min<graphene::chain::bet_multiplier_type>(current_bin->backer_multiplier, current_params.max_bet_multiplier);
current_bin->back_or_lay = bet_odds_iter->back_or_lay == bet_type::back ? bet_type::lay : bet_type::back; current_bin->back_or_lay = bet_odds_iter->back_or_lay == bet_type::back ? bet_type::lay : bet_type::back;
current_bin->amount_to_bet.amount = 0;
} }
current_bin->amount_to_bet.amount += bet_odds_iter->get_matching_amount(); current_bin->amount_to_bet.amount += bet_odds_iter->get_exact_matching_amount();
} }
if (current_bin) if (current_bin)
flush_current_bin(); flush_current_bin();

View file

@ -37,6 +37,7 @@
#include <graphene/chain/proposal_object.hpp> #include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/betting_market_object.hpp> #include <graphene/chain/betting_market_object.hpp>
#include <graphene/bookie/bookie_api.hpp>
//#include <boost/algorithm/string/replace.hpp> //#include <boost/algorithm/string/replace.hpp>
using namespace graphene::chain; using namespace graphene::chain;
@ -144,6 +145,95 @@ BOOST_AUTO_TEST_CASE(simple_bet_win)
} FC_LOG_AND_RETHROW() } FC_LOG_AND_RETHROW()
} }
BOOST_AUTO_TEST_CASE(binned_order_books)
{
try
{
ACTORS( (alice)(bob) );
CREATE_ICE_HOCKEY_BETTING_MARKET();
graphene::bookie::bookie_api bookie_api(app);
// give alice and bob 10k each
transfer(account_id_type(), alice_id, asset(10000));
transfer(account_id_type(), bob_id, asset(10000));
// place back bets at decimal odds of 1.55, 1.6, 1.65, 1.66, and 1.67
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 155 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2);
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 16 * GRAPHENE_BETTING_ODDS_PRECISION / 10, 2);
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 165 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2);
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 166 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2);
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 167 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2);
const auto& bet_odds_idx = db.get_index_type<bet_object_index>().indices().get<by_odds>();
auto bet_iter = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id));
while (bet_iter != bet_odds_idx.end() &&
bet_iter->betting_market_id == capitals_win_market.id)
{
idump((*bet_iter));
++bet_iter;
}
graphene::bookie::binned_order_book binned_orders_point_one = bookie_api.get_binned_order_book(capitals_win_market.id, 1);
idump((binned_orders_point_one));
// the binned orders returned should be chosen so that we if we assume those orders are real and we place
// matching lay orders, we will completely consume the underlying orders and leave no orders on the books
BOOST_CHECK_EQUAL(binned_orders_point_one.aggregated_back_bets.size(), 2);
BOOST_CHECK_EQUAL(binned_orders_point_one.aggregated_lay_bets.size(), 0);
for (const graphene::bookie::order_bin& binned_order : binned_orders_point_one.aggregated_back_bets)
{
// compute the matching lay order
share_type lay_amount = bet_object::get_approximate_matching_amount(binned_order.amount_to_bet, binned_order.backer_multiplier, bet_type::back, false /* round down */);
ilog("Alice is laying with ${lay_amount} at odds ${odds} to match the binned back amount ${back_amount}", ("lay_amount", lay_amount)("odds", binned_order.backer_multiplier)("back_amount", binned_order.amount_to_bet));
place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(lay_amount, asset_id_type()), binned_order.backer_multiplier, 2);
}
bet_iter = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id));
while (bet_iter != bet_odds_idx.end() &&
bet_iter->betting_market_id == capitals_win_market.id)
{
idump((*bet_iter));
++bet_iter;
}
BOOST_CHECK(bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id)) == bet_odds_idx.end());
// place lay bets at decimal odds of 1.55, 1.6, 1.65, 1.66, and 1.67
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 155 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2);
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 16 * GRAPHENE_BETTING_ODDS_PRECISION / 10, 2);
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 165 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2);
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 166 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2);
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 167 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2);
binned_orders_point_one = bookie_api.get_binned_order_book(capitals_win_market.id, 1);
idump((binned_orders_point_one));
// the binned orders returned should be chosen so that we if we assume those orders are real and we place
// matching lay orders, we will completely consume the underlying orders and leave no orders on the books
BOOST_CHECK_EQUAL(binned_orders_point_one.aggregated_back_bets.size(), 0);
BOOST_CHECK_EQUAL(binned_orders_point_one.aggregated_lay_bets.size(), 2);
for (const graphene::bookie::order_bin& binned_order : binned_orders_point_one.aggregated_lay_bets)
{
// compute the matching lay order
share_type back_amount = bet_object::get_approximate_matching_amount(binned_order.amount_to_bet, binned_order.backer_multiplier, bet_type::lay, false /* round down */);
ilog("Alice is backing with ${back_amount} at odds ${odds} to match the binned lay amount ${lay_amount}", ("back_amount", back_amount)("odds", binned_order.backer_multiplier)("lay_amount", binned_order.amount_to_bet));
place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(back_amount, asset_id_type()), binned_order.backer_multiplier, 2);
}
bet_iter = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id));
while (bet_iter != bet_odds_idx.end() &&
bet_iter->betting_market_id == capitals_win_market.id)
{
idump((*bet_iter));
++bet_iter;
}
BOOST_CHECK(bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id)) == bet_odds_idx.end());
} FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_CASE( peerplays_sport_create_test ) BOOST_AUTO_TEST_CASE( peerplays_sport_create_test )
{ {