Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#463, Add API to query for open orders of one account in one market #849

Merged
merged 18 commits into from
Jul 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions libraries/app/database_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,19 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>

// Markets / feeds
vector<limit_order_object> get_limit_orders(asset_id_type a, asset_id_type b, uint32_t limit)const;
vector<limit_order_object> get_account_limit_orders( const string& name_or_id,
const string &base,
const string &quote, uint32_t limit,
optional<limit_order_id_type> ostart_id,
optional<price> ostart_price );
vector<call_order_object> get_call_orders(asset_id_type a, uint32_t limit)const;
vector<force_settlement_object> get_settle_orders(asset_id_type a, uint32_t limit)const;
vector<call_order_object> get_margin_positions( const std::string account_id_or_name )const;
vector<collateral_bid_object> get_collateral_bids(const asset_id_type asset, uint32_t limit, uint32_t start)const;

void subscribe_to_market(std::function<void(const variant&)> callback, asset_id_type a, asset_id_type b);
void unsubscribe_from_market(asset_id_type a, asset_id_type b);

market_ticker get_ticker( const string& base, const string& quote, bool skip_order_book = false )const;
market_volume get_24_volume( const string& base, const string& quote )const;
order_book get_order_book( const string& base, const string& quote, unsigned limit = 50 )const;
Expand Down Expand Up @@ -655,6 +662,101 @@ vector<optional<account_object>> database_api_impl::get_accounts(const vector<st
return result;
}

vector<limit_order_object> database_api::get_account_limit_orders( const string& name_or_id, const string &base,
const string &quote, uint32_t limit, optional<limit_order_id_type> ostart_id, optional<price> ostart_price)
{
return my->get_account_limit_orders( name_or_id, base, quote, limit, ostart_id, ostart_price );
}

vector<limit_order_object> database_api_impl::get_account_limit_orders( const string& name_or_id, const string &base,
const string &quote, uint32_t limit, optional<limit_order_id_type> ostart_id, optional<price> ostart_price)
{
FC_ASSERT( limit <= 101 );

vector<limit_order_object> results;
const account_object* account = nullptr;
uint32_t count = 0;

if (std::isdigit(name_or_id[0]))
account = _db.find(fc::variant(name_or_id, 1).as<account_id_type>(1));
else
{
const auto& idx = _db.get_index_type<account_index>().indices().get<by_name>();
auto itr = idx.find(name_or_id);
if (itr != idx.end())
account = &*itr;
}
if (account == nullptr)
return results;

auto assets = lookup_asset_symbols( {base, quote} );
FC_ASSERT( assets[0], "Invalid base asset symbol: ${s}", ("s",base) );
FC_ASSERT( assets[1], "Invalid quote asset symbol: ${s}", ("s",quote) );

auto base_id = assets[0]->id;
auto quote_id = assets[1]->id;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the best place to check whether the start price is consistent with base and quote parameters. If not, IMHO best to throw out an exception.


if (ostart_price.valid()) {
FC_ASSERT(ostart_price->base.asset_id == base_id, "Base asset inconsistent with start price");
FC_ASSERT(ostart_price->quote.asset_id == quote_id, "Quote asset inconsistent with start price");
}

const auto& index_by_account = _db.get_index_type<limit_order_index>().indices().get<by_account>();
limit_order_multi_index_type::index<by_account>::type::const_iterator lower_itr;
limit_order_multi_index_type::index<by_account>::type::const_iterator upper_itr;
Copy link
Member

@abitmore abitmore May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually upper_itr can be initialized here, no need to be initialized in if/else block below, since the results will be the same.

Update: actually it's better to initialize it after the if/else block (than before the block).

Copy link
Member Author

@windycrypto windycrypto May 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has initialize upper_itr in each of if/else if/else branch, is it ok?


// if both order_id and price are invalid, query the first page
if ( !ostart_id.valid() && !ostart_price.valid() )
{
lower_itr = index_by_account.lower_bound(std::make_tuple(account->id, price::max(base_id, quote_id)));
}
else if ( ostart_id.valid() )
{
// in case of the order been deleted during page querying
const limit_order_object *p_loo = _db.find(*ostart_id);

if ( !p_loo )
{
if ( ostart_price.valid() )
{
lower_itr = index_by_account.lower_bound(std::make_tuple(account->id, *ostart_price, *ostart_id));
}
else
{
// start order id been deleted, yet not provided price either
FC_THROW("Order id invalid (maybe just been canceled?), and start price not provided");
}
}
else
{
const limit_order_object &loo = *p_loo;

// in case of the order not belongs to specified account or market
FC_ASSERT(loo.sell_price.base.asset_id == base_id, "Order base asset inconsistent");
FC_ASSERT(loo.sell_price.quote.asset_id == quote_id, "Order quote asset inconsistent with order");
FC_ASSERT(loo.seller == account->get_id(), "Order not owned by specified account");

lower_itr = index_by_account.lower_bound(std::make_tuple(account->id, loo.sell_price, *ostart_id));
}
}
else
{
// if reach here start_price must be valid
lower_itr = index_by_account.lower_bound(std::make_tuple(account->id, *ostart_price));
}

upper_itr = index_by_account.upper_bound(std::make_tuple(account->id, price::min(base_id, quote_id)));

// Add the account's orders
for ( ; lower_itr != upper_itr && count < limit; ++lower_itr, ++count)
{
const limit_order_object &order = *lower_itr;
results.emplace_back(order);
}

return results;
}

std::map<string,full_account> database_api::get_full_accounts( const vector<string>& names_or_ids, bool subscribe )
{
return my->get_full_accounts( names_or_ids, subscribe );
Expand Down
30 changes: 30 additions & 0 deletions libraries/app/include/graphene/app/database_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,35 @@ class database_api
*/
vector<optional<account_object>> get_accounts(const vector<std::string>& account_names_or_ids)const;

/**
* @brief Fetch all orders relevant to the specified account and specified market, result orders
* are sorted descendingly by price
*
* @param name_or_id The name or ID of an account to retrieve
* @param base Base asset
* @param quote Quote asset
* @param limit The limitation of items each query can fetch, not greater than 101
* @param start_id Start order id, fetch orders which price lower than this order, or price equal to this order
* but order ID greater than this order
* @param start_price Fetch orders with price lower than or equal to this price
*
* @return List of orders from @ref name_or_id to the corresponding account
*
* @note
* 1. if @ref name_or_id cannot be tied to an account, empty result will be returned
* 2. @ref start_id and @ref start_price can be empty, if so the api will return the "first page" of orders;
* if start_id is specified, its price will be used to do page query preferentially, otherwise the start_price
* will be used; start_id and start_price may be used cooperatively in case of the order specified by start_id
* was just canceled accidentally, in such case, the result orders' price may lower or equal to start_price,
* but orders' id greater than start_id
*/
vector<limit_order_object> get_account_limit_orders( const string& name_or_id,
const string &base,
const string &quote,
uint32_t limit = 101,
optional<limit_order_id_type> ostart_id = optional<limit_order_id_type>(),
optional<price> ostart_price = optional<price>());

/**
* @brief Fetch all objects relevant to the specified accounts and subscribe to updates
* @param callback Function to call with updates
Expand Down Expand Up @@ -727,6 +756,7 @@ FC_API(graphene::app::database_api,
// Accounts
(get_account_id_from_string)
(get_accounts)
(get_account_limit_orders)
(get_full_accounts)
(get_account_by_name)
(get_account_references)
Expand Down
4 changes: 3 additions & 1 deletion libraries/chain/include/graphene/chain/market_object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ typedef multi_index_container<
ordered_unique< tag<by_account>,
composite_key< limit_order_object,
member<limit_order_object, account_id_type, &limit_order_object::seller>,
member<limit_order_object, price, &limit_order_object::sell_price>,
member<object, object_id_type, &object::id>
>
>,
composite_key_compare<std::less<account_id_type>, std::greater<price>, std::less<object_id_type>>
>
>
> limit_order_multi_index_type;
Expand Down
27 changes: 27 additions & 0 deletions libraries/wallet/include/graphene/wallet/wallet.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,32 @@ class wallet_api
*/
full_account get_full_account( const string& name_or_id);
vector<bucket_object> get_market_history(string symbol, string symbol2, uint32_t bucket, fc::time_point_sec start, fc::time_point_sec end)const;

/**
* @brief Fetch all orders relevant to the specified account sorted descendingly by price
*
* @param name_or_id The name or ID of an account to retrieve
* @param base Base asset
* @param quote Quote asset
* @param limit The limitation of items each query can fetch, currently 101
* @param start_id Start order id, fetch orders which price are lower than or equal to this order
* @param start_price Fetch orders with price lower than or equal to this price
*
* @return List of orders from @ref name_or_id to the corresponding account
*
* @note
* 1. if @ref name_or_id cannot be tied to an account, empty result will be returned
* 2. @ref start_id and @ref start_price can be empty, if so the api will return the "first page" of orders;
* if start_id is specified and valid, its price will be used to do page query preferentially,
* otherwise the start_price will be used
*/
vector<limit_order_object> get_account_limit_orders( const string& name_or_id,
const string &base,
const string &quote,
uint32_t limit = 101,
optional<limit_order_id_type> ostart_id = optional<limit_order_id_type>(),
optional<price> ostart_price = optional<price>());

vector<limit_order_object> get_limit_orders(string a, string b, uint32_t limit)const;
vector<call_order_object> get_call_orders(string a, uint32_t limit)const;
vector<force_settlement_object> get_settle_orders(string a, uint32_t limit)const;
Expand Down Expand Up @@ -1781,6 +1807,7 @@ FC_API( graphene::wallet::wallet_api,
(get_private_key)
(load_wallet_file)
(normalize_brain_key)
(get_account_limit_orders)
(get_limit_orders)
(get_call_orders)
(get_settle_orders)
Expand Down
10 changes: 10 additions & 0 deletions libraries/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3044,6 +3044,16 @@ vector<bucket_object> wallet_api::get_market_history( string symbol1, string sym
return my->_remote_hist->get_market_history( get_asset_id(symbol1), get_asset_id(symbol2), bucket, start, end );
}

vector<limit_order_object> wallet_api::get_account_limit_orders( const string& name_or_id,
const string &base,
const string &quote,
uint32_t limit,
optional<limit_order_id_type> ostart_id,
optional<price> ostart_price)
{
return my->_remote_db->get_account_limit_orders(name_or_id, base, quote, limit, ostart_id, ostart_price);
}

vector<limit_order_object> wallet_api::get_limit_orders(string a, string b, uint32_t limit)const
{
return my->_remote_db->get_limit_orders(get_asset(a).id, get_asset(b).id, limit);
Expand Down
116 changes: 116 additions & 0 deletions tests/tests/database_api_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,121 @@ BOOST_AUTO_TEST_CASE( lookup_vote_ids )

} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_CASE(get_account_limit_orders)
{ try {

ACTORS((seller));

const auto& bitcny = create_bitasset("CNY");
const auto& core = asset_id_type()(db);

int64_t init_balance(10000000);
transfer(committee_account, seller_id, asset(init_balance));
BOOST_CHECK_EQUAL( 10000000, get_balance(seller, core) );

/// Create 250 versatile orders
for (int i = 0 ; i < 50 ; ++i)
{
BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250)));
}

for (int i = 1 ; i < 101 ; ++i)
{
BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250 + i)));
BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250 - i)));
}

graphene::app::database_api db_api(db);
std::vector<limit_order_object> results;
limit_order_object o;

// query with no constraint, expected:
// 1. up to 101 orders returned
// 2. orders were sorted by price desendingly
results = db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, "CNY");
BOOST_CHECK(results.size() == 101);
for (int i = 0 ; i < results.size() - 1 ; ++i)
{
BOOST_CHECK(results[i].sell_price >= results[i+1].sell_price);
}
BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(150)));
BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(250)));
results.clear();

// query with specified limit, expected:
// 1. up to specified amount of orders returned
// 2. orders were sorted by price desendingly
results = db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, "CNY", 50);
BOOST_CHECK(results.size() == 50);
for (int i = 0 ; i < results.size() - 1 ; ++i)
{
BOOST_CHECK(results[i].sell_price >= results[i+1].sell_price);
}
BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(150)));
BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(199)));

o = results.back();
results.clear();

// query with specified order id and limit, expected:
// same as before, but also the first order's id equal to specified
results = db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, "CNY", 80,
limit_order_id_type(o.id));
BOOST_CHECK(results.size() == 80);
BOOST_CHECK(results.front().id == o.id);
for (int i = 0 ; i < results.size() - 1 ; ++i)
{
BOOST_CHECK(results[i].sell_price >= results[i+1].sell_price);
}
BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(199)));
BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(250)));

o = results.back();
results.clear();

// query with specified price and an not exists order id, expected:
// 1. the canceled order should not exists in returned orders and first order's
// id should greater than specified
// 2. returned orders sorted by price desendingly
// 3. the first order's sell price equal to specified
cancel_limit_order(o); // NOTE 1: this canceled order was in scope of the
// first created 50 orders, so with price 2.5 BTS/CNY
results = db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, "CNY", 50,
limit_order_id_type(o.id), o.sell_price);
BOOST_CHECK(results.size() == 50);
BOOST_CHECK(results.front().id > o.id);
// NOTE 2: because of NOTE 1, here should be equal
BOOST_CHECK(results.front().sell_price == o.sell_price);
for (int i = 0 ; i < results.size() - 1 ; ++i)
{
BOOST_CHECK(results[i].sell_price >= results[i+1].sell_price);
}
BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(250)));
BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(279)));

o = results.back();
results.clear();

cancel_limit_order(o); // NOTE 3: this time the canceled order was in scope
// of the lowest price 150 orders
results = db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, "CNY", 101,
limit_order_id_type(o.id), o.sell_price);
BOOST_CHECK(results.size() == 71);
BOOST_CHECK(results.front().id > o.id);
// NOTE 3: because of NOTE 1, here should be little than
BOOST_CHECK(results.front().sell_price < o.sell_price);
for (int i = 0 ; i < results.size() - 1 ; ++i)
{
BOOST_CHECK(results[i].sell_price >= results[i+1].sell_price);
}
BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(280)));
BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(350)));

BOOST_CHECK_THROW(db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, "CNY", 101,
limit_order_id_type(o.id)), fc::exception);

} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_CASE( get_transaction_hex )
{ try {
graphene::app::database_api db_api(db);
Expand All @@ -715,6 +830,7 @@ BOOST_AUTO_TEST_CASE( get_transaction_hex )
BOOST_CHECK( db_api.get_transaction_hex( trx ) == hex_str );
BOOST_CHECK( db_api.get_transaction_hex_without_sig( trx ) +
fc::to_hex( fc::raw::pack( trx.signatures ) ) == hex_str );

} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_SUITE_END()