Change bet matching algorithm to limit the amount matched by the
taker's odds, instead of buying as much as the taker's bet_amount allowed at the maker's odds
This commit is contained in:
parent
2c50036ee1
commit
70e47a74dd
2 changed files with 479 additions and 39 deletions
|
|
@ -401,7 +401,8 @@ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker
|
|||
{
|
||||
assert(taker_bet.amount_to_bet.asset_id == maker_bet.amount_to_bet.asset_id);
|
||||
assert(taker_bet.amount_to_bet.amount > 0 && maker_bet.amount_to_bet.amount > 0);
|
||||
assert(taker_bet.back_or_lay == bet_type::back ? taker_bet.backer_multiplier <= maker_bet.backer_multiplier : taker_bet.backer_multiplier >= maker_bet.backer_multiplier);
|
||||
assert(taker_bet.back_or_lay == bet_type::back ? taker_bet.backer_multiplier <= maker_bet.backer_multiplier :
|
||||
taker_bet.backer_multiplier >= maker_bet.backer_multiplier);
|
||||
assert(taker_bet.back_or_lay != maker_bet.back_or_lay);
|
||||
|
||||
int result = 0;
|
||||
|
|
@ -416,17 +417,33 @@ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker
|
|||
// and make some shortcuts to get to the maker's and taker's side of the ratio
|
||||
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;
|
||||
// we need to figure out how much of the bet matches. the smallest amount
|
||||
// that could match is one maker_odds_ratio to one taker_odds_ratio,
|
||||
// but we can match any integer multiple of that ratio (called the 'factor' below),
|
||||
// limited only by the bet amounts.
|
||||
//
|
||||
//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_factor_taker_is_willing_to_pay = taker_bet.amount_to_bet.amount / taker_odds_ratio;
|
||||
share_type maximum_factor_taker_is_willing_to_receive = taker_bet.get_exact_matching_amount() / maker_odds_ratio;
|
||||
|
||||
share_type maximum_taker_factor;
|
||||
bool taker_was_limited_by_matching_amount = maximum_factor_taker_is_willing_to_receive < maximum_factor_taker_is_willing_to_pay;
|
||||
if (taker_was_limited_by_matching_amount)
|
||||
maximum_taker_factor = maximum_factor_taker_is_willing_to_receive;
|
||||
else
|
||||
maximum_taker_factor = maximum_factor_taker_is_willing_to_pay;
|
||||
|
||||
idump((maximum_factor_taker_is_willing_to_pay)(maximum_factor_taker_is_willing_to_receive)(maximum_taker_factor));
|
||||
|
||||
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));
|
||||
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);
|
||||
|
|
@ -451,6 +468,50 @@ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker
|
|||
// maker bets will always be an exact multiple of maker_odds_ratio, so they will either completely match or remain on the books
|
||||
bool maker_bet_will_completely_match = maker_amount_to_match == maker_bet.amount_to_bet.amount;
|
||||
|
||||
if (maker_bet_will_completely_match && taker_amount_to_match != taker_bet.amount_to_bet.amount)
|
||||
{
|
||||
// then the taker bet will stay on the books. If the taker odds != the maker odds, we will
|
||||
// need to refund the stake the taker was expecting to pay but didn't.
|
||||
// compute how much of the taker's bet should still be left on the books and how much
|
||||
// the taker should pay for the remaining amount; refund any amount that won't remain
|
||||
// on the books and isn't used to pay the bet we're currently matching.
|
||||
|
||||
share_type takers_odds_back_odds_ratio;
|
||||
share_type takers_odds_lay_odds_ratio;
|
||||
std::tie(takers_odds_back_odds_ratio, takers_odds_lay_odds_ratio) = taker_bet.get_ratio();
|
||||
const share_type& takers_odds_taker_odds_ratio = taker_bet.back_or_lay == bet_type::back ? takers_odds_back_odds_ratio : takers_odds_lay_odds_ratio;
|
||||
const share_type& takers_odds_maker_odds_ratio = taker_bet.back_or_lay == bet_type::back ? takers_odds_lay_odds_ratio : takers_odds_back_odds_ratio;
|
||||
|
||||
share_type unrounded_taker_remaining_amount_to_match = taker_bet.get_exact_matching_amount() - maker_amount_to_match;
|
||||
// because we matched at the maker's odds and not the taker's odds, the remaining amount to match
|
||||
// may not be an even multiple of the taker's odds; round it down.
|
||||
share_type taker_remaining_factor = unrounded_taker_remaining_amount_to_match / takers_odds_maker_odds_ratio;
|
||||
share_type taker_remaining_maker_amount_to_match = taker_remaining_factor * takers_odds_maker_odds_ratio;
|
||||
share_type taker_remaining_bet_amount = taker_remaining_factor * takers_odds_taker_odds_ratio;
|
||||
|
||||
share_type taker_refund_amount = taker_bet.amount_to_bet.amount - taker_amount_to_match - taker_remaining_bet_amount;
|
||||
|
||||
if (taker_refund_amount > share_type())
|
||||
{
|
||||
db.modify(taker_bet, [&taker_refund_amount](bet_object& taker_bet_object) {
|
||||
taker_bet_object.amount_to_bet.amount -= taker_refund_amount;
|
||||
});
|
||||
dlog("Refunding ${taker_refund_amount} to taker because we matched at the maker's odds of "
|
||||
"${maker_odds} instead of the taker's odds ${taker_odds}",
|
||||
("taker_refund_amount", taker_refund_amount)
|
||||
("maker_odds", maker_bet.backer_multiplier)
|
||||
("taker_odds", taker_bet.backer_multiplier));
|
||||
ddump((taker_bet));
|
||||
|
||||
db.adjust_balance(taker_bet.bettor_id, asset(taker_refund_amount, taker_bet.amount_to_bet.asset_id));
|
||||
// TODO: update global statistics
|
||||
bet_adjusted_operation bet_adjusted_op(taker_bet.bettor_id, taker_bet.id,
|
||||
asset(taker_refund_amount, taker_bet.amount_to_bet.asset_id));
|
||||
// idump((bet_adjusted_op)(new_bet_object));
|
||||
db.push_applied_operation(std::move(bet_adjusted_op));
|
||||
}
|
||||
}
|
||||
|
||||
// if the maker bet stays on the books, we need to make sure the taker bet is removed from the books (either it fills completely,
|
||||
// or any un-filled amount is canceled)
|
||||
result |= bet_was_matched(db, taker_bet, taker_amount_to_match, maker_amount_to_match, maker_bet.backer_multiplier, !maker_bet_will_completely_match);
|
||||
|
|
@ -464,6 +525,39 @@ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker
|
|||
// called from the bet_place_evaluator
|
||||
bool database::place_bet(const bet_object& new_bet_object)
|
||||
{
|
||||
// We allow users to place bets for any amount, but only amounts that are exact multiples of the odds
|
||||
// ratio can be matched. Immediately return any unmatchable amount in this bet.
|
||||
share_type minimum_matchable_amount = new_bet_object.get_minimum_matchable_amount();
|
||||
share_type scale_factor = new_bet_object.amount_to_bet.amount / minimum_matchable_amount;
|
||||
share_type rounded_bet_amount = scale_factor * minimum_matchable_amount;
|
||||
|
||||
if (rounded_bet_amount == share_type())
|
||||
{
|
||||
// the bet was too small to match at all, cancel the bet
|
||||
cancel_bet(new_bet_object, true);
|
||||
return true;
|
||||
}
|
||||
else if (rounded_bet_amount != new_bet_object.amount_to_bet.amount)
|
||||
{
|
||||
asset stake_returned = new_bet_object.amount_to_bet;
|
||||
stake_returned.amount -= rounded_bet_amount;
|
||||
|
||||
modify(new_bet_object, [&rounded_bet_amount](bet_object& modified_bet_object) {
|
||||
modified_bet_object.amount_to_bet.amount = rounded_bet_amount;
|
||||
});
|
||||
|
||||
adjust_balance(new_bet_object.bettor_id, stake_returned);
|
||||
// TODO: update global statistics
|
||||
bet_adjusted_operation bet_adjusted_op(new_bet_object.bettor_id, new_bet_object.id,
|
||||
stake_returned);
|
||||
// idump((bet_adjusted_op)(new_bet_object));
|
||||
push_applied_operation(std::move(bet_adjusted_op));
|
||||
|
||||
dlog("Refunded ${refund_amount} to round the bet down to something that can match exactly, new bet: ${new_bet}",
|
||||
("refund_amount", stake_returned.amount)
|
||||
("new_bet", new_bet_object));
|
||||
}
|
||||
|
||||
const auto& bet_odds_idx = get_index_type<bet_object_index>().indices().get<by_odds>();
|
||||
|
||||
bet_type bet_type_to_match = new_bet_object.back_or_lay == bet_type::back ? bet_type::lay : bet_type::back;
|
||||
|
|
@ -488,45 +582,12 @@ bool database::place_bet(const bet_object& new_bet_object)
|
|||
// we continue if the maker bet was completely consumed AND the taker bet was not
|
||||
finished = orders_matched_flags != 2;
|
||||
}
|
||||
|
||||
if (!(orders_matched_flags & 1))
|
||||
{
|
||||
// if the new (taker) bet was not completely consumed, we need to put whatever remains
|
||||
// of it on the books. But we only allow bets that can be exactly matched
|
||||
// on the books, so round the amount down if necessary
|
||||
share_type minimum_matchable_amount = new_bet_object.get_minimum_matchable_amount();
|
||||
share_type scale_factor = new_bet_object.amount_to_bet.amount / minimum_matchable_amount;
|
||||
share_type rounded_bet_amount = scale_factor * minimum_matchable_amount;
|
||||
//idump((new_bet_object.amount_to_bet.amount)(rounded_bet_amount)(minimum_matchable_amount)(scale_factor));
|
||||
ddump((new_bet_object));
|
||||
|
||||
if (rounded_bet_amount == share_type())
|
||||
{
|
||||
// the remainder of the bet was too small to match, cancel the bet
|
||||
cancel_bet(new_bet_object, true);
|
||||
return true;
|
||||
}
|
||||
else if (rounded_bet_amount != new_bet_object.amount_to_bet.amount)
|
||||
{
|
||||
asset stake_returned = new_bet_object.amount_to_bet;
|
||||
stake_returned.amount -= rounded_bet_amount;
|
||||
|
||||
modify(new_bet_object, [&rounded_bet_amount](bet_object& modified_bet_object) {
|
||||
modified_bet_object.amount_to_bet.amount = rounded_bet_amount;
|
||||
});
|
||||
|
||||
adjust_balance(new_bet_object.bettor_id, stake_returned);
|
||||
// TODO: update global statistics
|
||||
bet_adjusted_operation bet_adjusted_op(new_bet_object.bettor_id, new_bet_object.id,
|
||||
stake_returned);
|
||||
// idump((bet_adjusted_op)(new_bet_object));
|
||||
push_applied_operation(std::move(bet_adjusted_op));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
// return true if the taker bet was completely consumed
|
||||
return (orders_matched_flags & 1) != 0;
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -66,6 +66,75 @@ using namespace graphene::chain;
|
|||
using namespace graphene::chain::test;
|
||||
using namespace graphene::chain::keywords;
|
||||
|
||||
|
||||
// While the bets are placed, stored, and sorted using the decimal form of their odds, matching
|
||||
// uses the ratios to ensure no rounding takes place.
|
||||
// The allowed odds are defined by rules that can be changed at runtime.
|
||||
// For reference when designing/debugging tests, here is the list of allowed decimal odds and their
|
||||
// corresponding ratios as set in the genesis block.
|
||||
//
|
||||
// decimal ratio | decimal ratio | decimal ratio | decimal ratio | decimal ratio | decimal ratio
|
||||
// ------------------+-----------------+-------------------+-------------------+-------------------+-----------------
|
||||
// 1.01 100:1 | 1.6 5:3 | 2.38 50:69 | 4.8 5:19 | 26 1:25 | 440 1:439
|
||||
// 1.02 50:1 | 1.61 100:61 | 2.4 5:7 | 4.9 10:39 | 27 1:26 | 450 1:449
|
||||
// 1.03 100:3 | 1.62 50:31 | 2.42 50:71 | 5 1:4 | 28 1:27 | 460 1:459
|
||||
// 1.04 25:1 | 1.63 100:63 | 2.44 25:36 | 5.1 10:41 | 29 1:28 | 470 1:469
|
||||
// 1.05 20:1 | 1.64 25:16 | 2.46 50:73 | 5.2 5:21 | 30 1:29 | 480 1:479
|
||||
// 1.06 50:3 | 1.65 20:13 | 2.48 25:37 | 5.3 10:43 | 32 1:31 | 490 1:489
|
||||
// 1.07 100:7 | 1.66 50:33 | 2.5 2:3 | 5.4 5:22 | 34 1:33 | 500 1:499
|
||||
// 1.08 25:2 | 1.67 100:67 | 2.52 25:38 | 5.5 2:9 | 36 1:35 | 510 1:509
|
||||
// 1.09 100:9 | 1.68 25:17 | 2.54 50:77 | 5.6 5:23 | 38 1:37 | 520 1:519
|
||||
// 1.1 10:1 | 1.69 100:69 | 2.56 25:39 | 5.7 10:47 | 40 1:39 | 530 1:529
|
||||
// 1.11 100:11 | 1.7 10:7 | 2.58 50:79 | 5.8 5:24 | 42 1:41 | 540 1:539
|
||||
// 1.12 25:3 | 1.71 100:71 | 2.6 5:8 | 5.9 10:49 | 44 1:43 | 550 1:549
|
||||
// 1.13 100:13 | 1.72 25:18 | 2.62 50:81 | 6 1:5 | 46 1:45 | 560 1:559
|
||||
// 1.14 50:7 | 1.73 100:73 | 2.64 25:41 | 6.2 5:26 | 48 1:47 | 570 1:569
|
||||
// 1.15 20:3 | 1.74 50:37 | 2.66 50:83 | 6.4 5:27 | 50 1:49 | 580 1:579
|
||||
// 1.16 25:4 | 1.75 4:3 | 2.68 25:42 | 6.6 5:28 | 55 1:54 | 590 1:589
|
||||
// 1.17 100:17 | 1.76 25:19 | 2.7 10:17 | 6.8 5:29 | 60 1:59 | 600 1:599
|
||||
// 1.18 50:9 | 1.77 100:77 | 2.72 25:43 | 7 1:6 | 65 1:64 | 610 1:609
|
||||
// 1.19 100:19 | 1.78 50:39 | 2.74 50:87 | 7.2 5:31 | 70 1:69 | 620 1:619
|
||||
// 1.2 5:1 | 1.79 100:79 | 2.76 25:44 | 7.4 5:32 | 75 1:74 | 630 1:629
|
||||
// 1.21 100:21 | 1.8 5:4 | 2.78 50:89 | 7.6 5:33 | 80 1:79 | 640 1:639
|
||||
// 1.22 50:11 | 1.81 100:81 | 2.8 5:9 | 7.8 5:34 | 85 1:84 | 650 1:649
|
||||
// 1.23 100:23 | 1.82 50:41 | 2.82 50:91 | 8 1:7 | 90 1:89 | 660 1:659
|
||||
// 1.24 25:6 | 1.83 100:83 | 2.84 25:46 | 8.2 5:36 | 95 1:94 | 670 1:669
|
||||
// 1.25 4:1 | 1.84 25:21 | 2.86 50:93 | 8.4 5:37 | 100 1:99 | 680 1:679
|
||||
// 1.26 50:13 | 1.85 20:17 | 2.88 25:47 | 8.6 5:38 | 110 1:109 | 690 1:689
|
||||
// 1.27 100:27 | 1.86 50:43 | 2.9 10:19 | 8.8 5:39 | 120 1:119 | 700 1:699
|
||||
// 1.28 25:7 | 1.87 100:87 | 2.92 25:48 | 9 1:8 | 130 1:129 | 710 1:709
|
||||
// 1.29 100:29 | 1.88 25:22 | 2.94 50:97 | 9.2 5:41 | 140 1:139 | 720 1:719
|
||||
// 1.3 10:3 | 1.89 100:89 | 2.96 25:49 | 9.4 5:42 | 150 1:149 | 730 1:729
|
||||
// 1.31 100:31 | 1.9 10:9 | 2.98 50:99 | 9.6 5:43 | 160 1:159 | 740 1:739
|
||||
// 1.32 25:8 | 1.91 100:91 | 3 1:2 | 9.8 5:44 | 170 1:169 | 750 1:749
|
||||
// 1.33 100:33 | 1.92 25:23 | 3.05 20:41 | 10 1:9 | 180 1:179 | 760 1:759
|
||||
// 1.34 50:17 | 1.93 100:93 | 3.1 10:21 | 10.5 2:19 | 190 1:189 | 770 1:769
|
||||
// 1.35 20:7 | 1.94 50:47 | 3.15 20:43 | 11 1:10 | 200 1:199 | 780 1:779
|
||||
// 1.36 25:9 | 1.95 20:19 | 3.2 5:11 | 11.5 2:21 | 210 1:209 | 790 1:789
|
||||
// 1.37 100:37 | 1.96 25:24 | 3.25 4:9 | 12 1:11 | 220 1:219 | 800 1:799
|
||||
// 1.38 50:19 | 1.97 100:97 | 3.3 10:23 | 12.5 2:23 | 230 1:229 | 810 1:809
|
||||
// 1.39 100:39 | 1.98 50:49 | 3.35 20:47 | 13 1:12 | 240 1:239 | 820 1:819
|
||||
// 1.4 5:2 | 1.99 100:99 | 3.4 5:12 | 13.5 2:25 | 250 1:249 | 830 1:829
|
||||
// 1.41 100:41 | 2 1:1 | 3.45 20:49 | 14 1:13 | 260 1:259 | 840 1:839
|
||||
// 1.42 50:21 | 2.02 50:51 | 3.5 2:5 | 14.5 2:27 | 270 1:269 | 850 1:849
|
||||
// 1.43 100:43 | 2.04 25:26 | 3.55 20:51 | 15 1:14 | 280 1:279 | 860 1:859
|
||||
// 1.44 25:11 | 2.06 50:53 | 3.6 5:13 | 15.5 2:29 | 290 1:289 | 870 1:869
|
||||
// 1.45 20:9 | 2.08 25:27 | 3.65 20:53 | 16 1:15 | 300 1:299 | 880 1:879
|
||||
// 1.46 50:23 | 2.1 10:11 | 3.7 10:27 | 16.5 2:31 | 310 1:309 | 890 1:889
|
||||
// 1.47 100:47 | 2.12 25:28 | 3.75 4:11 | 17 1:16 | 320 1:319 | 900 1:899
|
||||
// 1.48 25:12 | 2.14 50:57 | 3.8 5:14 | 17.5 2:33 | 330 1:329 | 910 1:909
|
||||
// 1.49 100:49 | 2.16 25:29 | 3.85 20:57 | 18 1:17 | 340 1:339 | 920 1:919
|
||||
// 1.5 2:1 | 2.18 50:59 | 3.9 10:29 | 18.5 2:35 | 350 1:349 | 930 1:929
|
||||
// 1.51 100:51 | 2.2 5:6 | 3.95 20:59 | 19 1:18 | 360 1:359 | 940 1:939
|
||||
// 1.52 25:13 | 2.22 50:61 | 4 1:3 | 19.5 2:37 | 370 1:369 | 950 1:949
|
||||
// 1.53 100:53 | 2.24 25:31 | 4.1 10:31 | 20 1:19 | 380 1:379 | 960 1:959
|
||||
// 1.54 50:27 | 2.26 50:63 | 4.2 5:16 | 21 1:20 | 390 1:389 | 970 1:969
|
||||
// 1.55 20:11 | 2.28 25:32 | 4.3 10:33 | 22 1:21 | 400 1:399 | 980 1:979
|
||||
// 1.56 25:14 | 2.3 10:13 | 4.4 5:17 | 23 1:22 | 410 1:409 | 990 1:989
|
||||
// 1.57 100:57 | 2.32 25:33 | 4.5 2:7 | 24 1:23 | 420 1:419 | 1000 1:999
|
||||
// 1.58 50:29 | 2.34 50:67 | 4.6 5:18 | 25 1:24 | 430 1:429 |
|
||||
// 1.59 100:59 | 2.36 25:34 | 4.7 10:37
|
||||
|
||||
#define CREATE_ICE_HOCKEY_BETTING_MARKET(never_in_play, delay_before_settling) \
|
||||
create_sport({{"en", "Ice Hockey"}, {"zh_Hans", "冰球"}, {"ja", "アイスホッケー"}}); \
|
||||
generate_blocks(1); \
|
||||
|
|
@ -410,6 +479,218 @@ BOOST_AUTO_TEST_CASE( cancel_unmatched_in_betting_group_test )
|
|||
} FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(match_using_takers_expected_amounts)
|
||||
{
|
||||
try
|
||||
{
|
||||
generate_blocks(1);
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(10000000));
|
||||
share_type alice_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// lay 46 at 1.94 odds (50:47) -- this is too small to be placed on the books and there's
|
||||
// nothing for it to match, so it should be canceled
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(46, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// lay 47 at 1.94 odds (50:47) -- this is an exact amount, nothing surprising should happen here
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(47, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
alice_expected_balance -= 47;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// lay 100 at 1.91 odds (100:91) -- this is an inexact match, we should get refunded 9 and leave a bet for 91 on the books
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 191 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
alice_expected_balance -= 91;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
|
||||
transfer(account_id_type(), bob_id, asset(10000000));
|
||||
share_type bob_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
|
||||
// now have bob match it with a back of 300 at 1.5
|
||||
// This should:
|
||||
// match the full 47 @ 1.94 with 50, and be refunded 44 (leaving 206 left in the bet)
|
||||
// match the full 91 @ 1.91 with 100, and be refunded 82 (leaving 24 in the bet)
|
||||
// bob's balance goes down by 300 - 44 - 82 = 124
|
||||
// leaves a back bet of 24 @ 1.5 on the books
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(300, asset_id_type()), 15 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
bob_expected_balance -= 300 - 44 - 82;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(match_using_takers_expected_amounts2)
|
||||
{
|
||||
try
|
||||
{
|
||||
generate_blocks(1);
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(10000000));
|
||||
share_type alice_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// lay 470 at 1.94 odds (50:47) -- this is an exact amount, nothing surprising should happen here
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(470, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
alice_expected_balance -= 470;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
transfer(account_id_type(), bob_id, asset(10000000));
|
||||
share_type bob_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
|
||||
// now have bob match it with a back of 900 at 1.5
|
||||
// This should:
|
||||
// match 423 out of the 470 @ 1.94 with 450, and be refunded 396 (leaving 54 left in the bet)
|
||||
// this is as close as bob can get without getting of a position than he planned, so the remainder
|
||||
// of bob's bet is canceled (refunding the remaining 54)
|
||||
// bob's balance goes down by the 450 he paid matching the bet.
|
||||
// alice's bet remains on the books as a lay of 47 @ 1.94
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(900, asset_id_type()), 15 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
bob_expected_balance -= 900 - 396 - 54;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(match_using_takers_expected_amounts3)
|
||||
{
|
||||
try
|
||||
{
|
||||
generate_blocks(1);
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(10000000));
|
||||
share_type alice_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// lay 470 at 1.94 odds (50:47) -- this is an exact amount, nothing surprising should happen here
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(470, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
alice_expected_balance -= 470;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
transfer(account_id_type(), bob_id, asset(10000000));
|
||||
share_type bob_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
|
||||
// now have bob match it with a back of 1000 at 1.5
|
||||
// This should:
|
||||
// match all of the 470 @ 1.94 with 500, and be refunded 440 (leaving 60 left in the bet)
|
||||
// bob's balance goes down by the 500 he paid matching the bet and the 60 that is left.
|
||||
// alice's bet is removed from the books
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(1000, asset_id_type()), 15 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
bob_expected_balance -= 1000 - 440;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(match_using_takers_expected_amounts4)
|
||||
{
|
||||
try
|
||||
{
|
||||
generate_blocks(1);
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(10000000));
|
||||
share_type alice_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// back 1000 at 1.89 odds (100:89) -- this is an exact amount, nothing surprising should happen here
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000, asset_id_type()), 189 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
alice_expected_balance -= 1000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// back 1000 at 1.97 odds (100:97) -- again, this is an exact amount, nothing surprising should happen here
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000, asset_id_type()), 197 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
alice_expected_balance -= 1000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
transfer(account_id_type(), bob_id, asset(10000000));
|
||||
share_type bob_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
|
||||
// now have bob match it with a lay of 3000 at 2.66
|
||||
// * This means bob expects to pay 3000 and match against 1807.2289. Or,
|
||||
// put another way, bob wants to buy a payout of up to 1807.2289 if the
|
||||
// capitals win, and he is willing to pay up to 3000 to do so.
|
||||
// * The first thing that happens is bob's bet gets rounded down to something
|
||||
// that can match exactly. 2.66 is 50:83 odds, so bob's bet is
|
||||
// reduced to 2988, which should match against 1800.
|
||||
// So bob gets an immediate refund of 12
|
||||
// * The next thing that happens is a match against the top bet on the order book.
|
||||
// That's 1000 @ 1.89. We match at those odds (100:89), so bob will fully match
|
||||
// this bet, paying 890 to get 1000 of his desired win position.
|
||||
// * this top bet is removed from the order books
|
||||
// * bob now has 1000 of the 1800 win position he wants. we adjust his bet
|
||||
// so that what is left will only match 800. This means we will
|
||||
// refund bob 770. His remaining bet is now lay 1328 @ 2.66
|
||||
// * Now we match the next bet on the order books, which is 1000 @ 1.97 (100:97).
|
||||
// Bob only wants 800 of 1000 win position being offered, so he will not
|
||||
// completely consume this bet.
|
||||
// * Bob pays 776 to get his 800 win position.
|
||||
// * alice's top bet on the books is reduced 200 @ 1.97
|
||||
// * Bob now has as much of a position as he was willing to buy. We refund
|
||||
// the remainder of his bet, which is 552
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(3000, asset_id_type()), 266 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
bob_expected_balance -= 3000 - 12 - 770 - 552;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(match_using_takers_expected_amounts5)
|
||||
{
|
||||
try
|
||||
{
|
||||
generate_blocks(1);
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(10000000));
|
||||
share_type alice_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// back 1100 at 1.86 odds (50:43) -- this is an exact amount, nothing surprising should happen here
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1100, asset_id_type()), 186 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
alice_expected_balance -= 1100;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
transfer(account_id_type(), bob_id, asset(10000000));
|
||||
share_type bob_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
|
||||
// now have bob match it with a lay of 1100 at 1.98
|
||||
// * This means bob expects to pay 1100 and match against 1122.4, Or,
|
||||
// put another way, bob wants to buy a payout of up to 1122.4 if the
|
||||
// capitals win, and he is willing to pay up to 1100 to do so.
|
||||
// * The first thing that happens is bob's bet gets rounded down to something
|
||||
// that can match exactly. 1.98 (50:49) odds, so bob's bet is
|
||||
// reduced to 1078, which should match against 1100.
|
||||
// So bob gets an immediate refund of 22
|
||||
// * The next thing that happens is a match against the top bet on the order book.
|
||||
// That's 1100 @ 1.86, At these odds, bob's 980 can buy all 1100 of bet, he
|
||||
// pays 1100:946.
|
||||
//
|
||||
// * alice's bet is fully matched, it is removed from the books
|
||||
// * bob's bet is fully matched, he is refunded the remaining 132 and his
|
||||
// bet is complete
|
||||
// * bob's balance is reduced by the 946 he paid
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1100, asset_id_type()), 198 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
bob_expected_balance -= 1100 - 22 - 132;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(inexact_odds)
|
||||
{
|
||||
try
|
||||
|
|
@ -618,6 +899,104 @@ BOOST_AUTO_TEST_CASE(persistent_objects_test)
|
|||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_settled_market_states)
|
||||
{
|
||||
try
|
||||
{
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
graphene::bookie::bookie_api bookie_api(app);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(10000000));
|
||||
transfer(account_id_type(), bob_id, asset(10000000));
|
||||
share_type alice_expected_balance = 10000000;
|
||||
share_type bob_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
idump((capitals_win_market.get_status()));
|
||||
|
||||
// lay 46 at 1.94 odds (50:47) -- this is too small to be placed on the books and there's
|
||||
// nothing for it to match, so it should be canceled
|
||||
bet_id_type automatically_canceled_bet_id = place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(46, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
generate_blocks(1);
|
||||
BOOST_CHECK_MESSAGE(!db.find(automatically_canceled_bet_id), "Bet should have been canceled, but the blockchain still knows about it");
|
||||
fc::variants objects_from_bookie = bookie_api.get_objects({automatically_canceled_bet_id});
|
||||
idump((objects_from_bookie));
|
||||
BOOST_REQUIRE_EQUAL(objects_from_bookie.size(), 1u);
|
||||
BOOST_CHECK_MESSAGE(objects_from_bookie[0]["id"].as<bet_id_type>() == automatically_canceled_bet_id, "Bookie Plugin didn't return a deleted bet it");
|
||||
|
||||
// lay 47 at 1.94 odds (50:47) -- this bet should go on the order books normally
|
||||
bet_id_type first_bet_on_books = place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(47, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
generate_blocks(1);
|
||||
BOOST_CHECK_MESSAGE(db.find(first_bet_on_books), "Bet should exist on the blockchain");
|
||||
objects_from_bookie = bookie_api.get_objects({first_bet_on_books});
|
||||
idump((objects_from_bookie));
|
||||
BOOST_REQUIRE_EQUAL(objects_from_bookie.size(), 1u);
|
||||
BOOST_CHECK_MESSAGE(objects_from_bookie[0]["id"].as<bet_id_type>() == first_bet_on_books, "Bookie Plugin didn't return a bet that is currently on the books");
|
||||
|
||||
// place a bet that exactly matches 'first_bet_on_books', should result in empty books (thus, no bet_objects from the blockchain)
|
||||
bet_id_type matching_bet = place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(50, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
BOOST_CHECK_MESSAGE(!db.find(first_bet_on_books), "Bet should have been filled, but the blockchain still knows about it");
|
||||
BOOST_CHECK_MESSAGE(!db.find(matching_bet), "Bet should have been filled, but the blockchain still knows about it");
|
||||
generate_blocks(1); // the bookie plugin doesn't detect matches until a block is generated
|
||||
|
||||
objects_from_bookie = bookie_api.get_objects({first_bet_on_books, matching_bet});
|
||||
idump((objects_from_bookie));
|
||||
BOOST_REQUIRE_EQUAL(objects_from_bookie.size(), 2u);
|
||||
BOOST_CHECK_MESSAGE(objects_from_bookie[0]["id"].as<bet_id_type>() == first_bet_on_books, "Bookie Plugin didn't return a bet that has been filled");
|
||||
BOOST_CHECK_MESSAGE(objects_from_bookie[1]["id"].as<bet_id_type>() == matching_bet, "Bookie Plugin didn't return a bet that has been filled");
|
||||
|
||||
update_betting_market_group(moneyline_betting_markets.id, _status = betting_market_group_status::closed);
|
||||
|
||||
resolve_betting_market_group(moneyline_betting_markets.id,
|
||||
{{capitals_win_market.id, betting_market_resolution_type::cancel},
|
||||
{blackhawks_win_market.id, betting_market_resolution_type::cancel}});
|
||||
|
||||
// as soon as the market is resolved during the generate_block(), these markets
|
||||
// should be deleted and our references will go out of scope. Save the
|
||||
// market ids here so we can verify that they were really deleted
|
||||
betting_market_id_type capitals_win_market_id = capitals_win_market.id;
|
||||
betting_market_id_type blackhawks_win_market_id = blackhawks_win_market.id;
|
||||
|
||||
generate_blocks(1);
|
||||
|
||||
// test get_matched_bets_for_bettor
|
||||
std::vector<graphene::bookie::matched_bet_object> alice_matched_bets = bookie_api.get_matched_bets_for_bettor(alice_id);
|
||||
for (const graphene::bookie::matched_bet_object& matched_bet : alice_matched_bets)
|
||||
{
|
||||
idump((matched_bet));
|
||||
for (operation_history_id_type id : matched_bet.associated_operations)
|
||||
idump((id(db)));
|
||||
}
|
||||
BOOST_REQUIRE_EQUAL(alice_matched_bets.size(), 1u);
|
||||
BOOST_CHECK(alice_matched_bets[0].amount_matched == 47);
|
||||
std::vector<graphene::bookie::matched_bet_object> bob_matched_bets = bookie_api.get_matched_bets_for_bettor(bob_id);
|
||||
for (const graphene::bookie::matched_bet_object& matched_bet : bob_matched_bets)
|
||||
{
|
||||
idump((matched_bet));
|
||||
for (operation_history_id_type id : matched_bet.associated_operations)
|
||||
idump((id(db)));
|
||||
}
|
||||
BOOST_REQUIRE_EQUAL(bob_matched_bets.size(), 1u);
|
||||
BOOST_CHECK(bob_matched_bets[0].amount_matched == 50);
|
||||
|
||||
// test getting markets
|
||||
// test that we cannot get them from the database directly
|
||||
BOOST_CHECK_THROW(capitals_win_market_id(db), fc::exception);
|
||||
BOOST_CHECK_THROW(blackhawks_win_market_id(db), fc::exception);
|
||||
|
||||
objects_from_bookie = bookie_api.get_objects({capitals_win_market_id, blackhawks_win_market_id});
|
||||
BOOST_REQUIRE_EQUAL(objects_from_bookie.size(), 2u);
|
||||
idump((objects_from_bookie));
|
||||
BOOST_CHECK(!objects_from_bookie[0].is_null());
|
||||
BOOST_CHECK(!objects_from_bookie[1].is_null());
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(delayed_bets_test) // test live betting
|
||||
{
|
||||
try
|
||||
|
|
|
|||
Loading…
Reference in a new issue