Merge branch graphene/develop into bitshares at commit '399e0ed9f970908ea7c2c5c43a64a28303c143b8'
This commit is contained in:
commit
ce8a6ec518
5 changed files with 356 additions and 14 deletions
|
|
@ -342,13 +342,12 @@ namespace graphene { namespace app {
|
|||
uint32_t count = 0;
|
||||
auto itr = history_idx.lower_bound( hkey );
|
||||
vector<order_history_object> result;
|
||||
while( itr != history_idx.end() )
|
||||
while( itr != history_idx.end() && count < limit)
|
||||
{
|
||||
if( itr->key.base != a || itr->key.quote != b ) break;
|
||||
result.push_back( *itr );
|
||||
++itr;
|
||||
++count;
|
||||
if( count > limit ) break;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -34,18 +34,22 @@
|
|||
|
||||
#include <cctype>
|
||||
|
||||
#include <cfenv>
|
||||
#include <iostream>
|
||||
|
||||
#define GET_REQUIRED_FEES_MAX_RECURSION 4
|
||||
|
||||
namespace graphene { namespace app {
|
||||
|
||||
class database_api_impl;
|
||||
|
||||
|
||||
class database_api_impl : public std::enable_shared_from_this<database_api_impl>
|
||||
{
|
||||
public:
|
||||
database_api_impl( graphene::chain::database& db );
|
||||
~database_api_impl();
|
||||
|
||||
|
||||
// Objects
|
||||
fc::variants get_objects(const vector<object_id_type>& ids)const;
|
||||
|
||||
|
|
@ -88,16 +92,20 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>
|
|||
|
||||
// Assets
|
||||
vector<optional<asset_object>> get_assets(const vector<asset_id_type>& asset_ids)const;
|
||||
vector<asset_object> list_assets(const string& lower_bound_symbol, uint32_t limit)const;
|
||||
vector<asset_object> list_assets(const string& lower_bound_symbol, uint32_t limit)const;
|
||||
vector<optional<asset_object>> lookup_asset_symbols(const vector<string>& symbols_or_ids)const;
|
||||
|
||||
// Markets / feeds
|
||||
vector<limit_order_object> get_limit_orders(asset_id_type a, asset_id_type b, uint32_t limit)const;
|
||||
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 account_id_type& id )const;
|
||||
vector<limit_order_object> get_limit_orders(asset_id_type a, asset_id_type b, uint32_t limit)const;
|
||||
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 account_id_type& id )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 )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;
|
||||
vector<market_trade> get_trade_history( const string& base, const string& quote, fc::time_point_sec start, fc::time_point_sec stop, unsigned limit = 100 )const;
|
||||
|
||||
// Witnesses
|
||||
vector<optional<witness_object>> get_witnesses(const vector<witness_id_type>& witness_ids)const;
|
||||
|
|
@ -195,7 +203,7 @@ database_api_impl::database_api_impl( graphene::chain::database& db ):_db(db)
|
|||
});
|
||||
_applied_block_connection = _db.applied_block.connect([this](const signed_block&){ on_applied_block(); });
|
||||
|
||||
_pending_trx_connection = _db.on_pending_transaction.connect([this](const signed_transaction& trx ){
|
||||
_pending_trx_connection = _db.on_pending_transaction.connect([this](const signed_transaction& trx ){
|
||||
if( _pending_trx_callback ) _pending_trx_callback( fc::variant(trx) );
|
||||
});
|
||||
}
|
||||
|
|
@ -461,7 +469,7 @@ vector<vector<account_id_type>> database_api_impl::get_key_references( vector<pu
|
|||
{
|
||||
wdump((a)(item)(item(_db).name));
|
||||
result.push_back(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -988,6 +996,254 @@ void database_api_impl::unsubscribe_from_market(asset_id_type a, asset_id_type b
|
|||
_market_subscriptions.erase(std::make_pair(a,b));
|
||||
}
|
||||
|
||||
market_ticker database_api::get_ticker( const string& base, const string& quote )const
|
||||
{
|
||||
return my->get_ticker( base, quote );
|
||||
}
|
||||
|
||||
market_ticker database_api_impl::get_ticker( const string& base, const string& quote )const
|
||||
{
|
||||
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;
|
||||
|
||||
market_ticker result;
|
||||
|
||||
result.base = base;
|
||||
result.quote = quote;
|
||||
result.base_volume = 0;
|
||||
result.quote_volume = 0;
|
||||
result.percent_change = 0;
|
||||
result.lowest_ask = 0;
|
||||
result.highest_bid = 0;
|
||||
|
||||
auto price_to_real = [&]( const long a, int p ) { return double( a ) / pow( 10, p ); };
|
||||
|
||||
try {
|
||||
if( base_id > quote_id ) std::swap(base_id, quote_id);
|
||||
|
||||
const auto& bidx = _db.get_index_type<bucket_index>();
|
||||
const auto& by_key_idx = bidx.indices().get<by_key>();
|
||||
uint32_t bucket_size = 86400;
|
||||
auto now = fc::time_point_sec( fc::time_point::now() );
|
||||
|
||||
auto itr = by_key_idx.lower_bound( bucket_key( base_id, quote_id, bucket_size,
|
||||
now - bucket_size ) );
|
||||
|
||||
if( itr != by_key_idx.end() && itr->key.base == base_id && itr->key.quote == quote_id && itr->key.seconds == bucket_size )
|
||||
{
|
||||
auto trades = get_trade_history( base, quote, now, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 );
|
||||
|
||||
if (assets[0]->id == base_id)
|
||||
{
|
||||
result.percent_change = ( ( price_to_real( itr->close_quote.value, assets[1]->precision ) / price_to_real( itr->close_base.value, assets[0]->precision ) )
|
||||
/ ( price_to_real( itr->open_quote.value, assets[1]->precision ) / price_to_real( itr->open_base.value, assets[0]->precision ) ) - 1 ) * 100;
|
||||
result.lowest_ask = (double) itr->low_quote.value / (double) itr->low_base.value;
|
||||
result.highest_bid = (double) itr->high_quote.value / (double) itr->high_base.value;
|
||||
result.latest = trades[0].price;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.percent_change = ( ( price_to_real( itr->close_base.value, assets[1]->precision ) / price_to_real( itr->close_quote.value, assets[0]->precision ) )
|
||||
/ ( price_to_real( itr->open_base.value, assets[1]->precision ) / price_to_real( itr->open_quote.value, assets[0]->precision ) ) - 1) * 100;
|
||||
result.lowest_ask = (double) itr->low_base.value / (double) itr->low_quote.value;
|
||||
result.highest_bid = (double) itr->high_base.value / (double) itr->high_quote.value;
|
||||
result.latest = trades[0].price;
|
||||
}
|
||||
|
||||
for ( market_trade t: trades )
|
||||
{
|
||||
result.base_volume += t.amount;
|
||||
result.quote_volume += t.value;
|
||||
}
|
||||
|
||||
while (trades.size() == 100)
|
||||
{
|
||||
for ( market_trade t: trades )
|
||||
{
|
||||
result.base_volume += t.amount;
|
||||
result.quote_volume += t.value;
|
||||
}
|
||||
|
||||
trades = get_trade_history( base, quote, trades[99].date, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 );
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} FC_CAPTURE_AND_RETHROW( (base)(quote) )
|
||||
}
|
||||
|
||||
market_volume database_api::get_24_volume( const string& base, const string& quote )const
|
||||
{
|
||||
return my->get_24_volume( base, quote );
|
||||
}
|
||||
|
||||
market_volume database_api_impl::get_24_volume( const string& base, const string& quote )const
|
||||
{
|
||||
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;
|
||||
|
||||
market_volume result;
|
||||
result.base = base;
|
||||
result.quote = quote;
|
||||
result.base_volume = 0;
|
||||
result.quote_volume = 0;
|
||||
|
||||
try {
|
||||
if( base_id > quote_id ) std::swap(base_id, quote_id);
|
||||
|
||||
uint32_t bucket_size = 86400;
|
||||
auto now = fc::time_point_sec( fc::time_point::now() );
|
||||
|
||||
auto trades = get_trade_history( base, quote, now, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 );
|
||||
|
||||
for ( market_trade t: trades )
|
||||
{
|
||||
result.base_volume += t.amount;
|
||||
result.quote_volume += t.value;
|
||||
}
|
||||
|
||||
while (trades.size() == 100)
|
||||
{
|
||||
for ( market_trade t: trades )
|
||||
{
|
||||
result.base_volume += t.amount;
|
||||
result.quote_volume += t.value;
|
||||
}
|
||||
|
||||
trades = get_trade_history( base, quote, trades[99].date, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 );
|
||||
}
|
||||
|
||||
return result;
|
||||
} FC_CAPTURE_AND_RETHROW( (base)(quote) )
|
||||
}
|
||||
|
||||
order_book database_api::get_order_book( const string& base, const string& quote, unsigned limit )const
|
||||
{
|
||||
return my->get_order_book( base, quote, limit);
|
||||
}
|
||||
|
||||
order_book database_api_impl::get_order_book( const string& base, const string& quote, unsigned limit )const
|
||||
{
|
||||
FC_ASSERT( limit <= 50 );
|
||||
|
||||
order_book result;
|
||||
result.base = base;
|
||||
result.quote = quote;
|
||||
|
||||
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;
|
||||
auto orders = get_limit_orders( base_id, quote_id, limit );
|
||||
|
||||
|
||||
auto asset_to_real = [&]( const asset& a, int p ) { return double(a.amount.value)/pow( 10, p ); };
|
||||
auto price_to_real = [&]( const price& p )
|
||||
{
|
||||
if( p.base.asset_id == base_id )
|
||||
return asset_to_real( p.quote, assets[1]->precision ) / asset_to_real( p.base, assets[0]->precision );
|
||||
else
|
||||
return asset_to_real( p.base, assets[1]->precision ) / asset_to_real( p.quote, assets[0]->precision );
|
||||
};
|
||||
|
||||
for( const auto& o : orders ) {
|
||||
if( o.sell_price.base.asset_id == base_id )
|
||||
{
|
||||
result.asks.push_back( std::make_pair( price_to_real(o.sell_price),
|
||||
asset_to_real(o.sell_price.base, assets[0]->precision)) );
|
||||
}
|
||||
else
|
||||
{
|
||||
result.bids.push_back( std::make_pair( price_to_real(o.sell_price),
|
||||
asset_to_real(o.sell_price.quote, assets[0]->precision ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
vector<market_trade> database_api::get_trade_history( const string& base,
|
||||
const string& quote,
|
||||
fc::time_point_sec start,
|
||||
fc::time_point_sec stop,
|
||||
unsigned limit )const
|
||||
{
|
||||
return my->get_trade_history( base, quote, start, stop, limit );
|
||||
}
|
||||
|
||||
vector<market_trade> database_api_impl::get_trade_history( const string& base,
|
||||
const string& quote,
|
||||
fc::time_point_sec start,
|
||||
fc::time_point_sec stop,
|
||||
unsigned limit )const
|
||||
{
|
||||
FC_ASSERT( limit <= 100 );
|
||||
|
||||
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;
|
||||
|
||||
if( base_id > quote_id ) std::swap( base_id, quote_id );
|
||||
const auto& history_idx = _db.get_index_type<graphene::market_history::history_index>().indices().get<by_key>();
|
||||
history_key hkey;
|
||||
hkey.base = base_id;
|
||||
hkey.quote = quote_id;
|
||||
hkey.sequence = std::numeric_limits<int64_t>::min();
|
||||
|
||||
auto price_to_real = [&]( const long long a, int p ) { return double( a ) / pow( 10, p ); };
|
||||
|
||||
if ( start.sec_since_epoch() == 0 )
|
||||
start = fc::time_point_sec( fc::time_point::now() );
|
||||
|
||||
uint32_t count = 0;
|
||||
auto itr = history_idx.lower_bound( hkey );
|
||||
vector<market_trade> result;
|
||||
|
||||
while( itr != history_idx.end() && count < limit && !( itr->key.base != base_id || itr->key.quote != quote_id || itr->time < stop ) )
|
||||
{
|
||||
if( itr->time < start )
|
||||
{
|
||||
market_trade trade;
|
||||
|
||||
if( base_id == itr->op.receives.asset_id )
|
||||
{
|
||||
trade.amount = price_to_real( itr->op.receives.amount.value, assets[0]->precision );
|
||||
trade.value = price_to_real( itr->op.pays.amount.value, assets[1]->precision );
|
||||
}
|
||||
else
|
||||
{
|
||||
trade.amount = price_to_real( itr->op.pays.amount.value, assets[0]->precision );
|
||||
trade.value = price_to_real( itr->op.receives.amount.value, assets[1]->precision );
|
||||
}
|
||||
|
||||
trade.date = itr->time;
|
||||
trade.price = trade.value / trade.amount;
|
||||
|
||||
result.push_back( trade );
|
||||
++count;
|
||||
}
|
||||
|
||||
// Trades are tracked in each direction.
|
||||
++itr;
|
||||
++itr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// Witnesses //
|
||||
|
|
@ -1500,7 +1756,7 @@ vector<blinded_balance_object> database_api_impl::get_blinded_balances( const fl
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// Private methods //
|
||||
// //
|
||||
// //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
void database_api_impl::broadcast_updates( const vector<variant>& updates )
|
||||
|
|
@ -1577,7 +1833,7 @@ void database_api_impl::on_objects_changed(const vector<object_id_type>& ids)
|
|||
|
||||
if( _market_subscriptions.size() )
|
||||
{
|
||||
if( !_subscribe_callback )
|
||||
if( !_subscribe_callback )
|
||||
obj = _db.find_object( id );
|
||||
if( obj )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@
|
|||
#include <graphene/chain/witness_object.hpp>
|
||||
#include <graphene/chain/confidential_evaluator.hpp>
|
||||
|
||||
#include <graphene/market_history/market_history_plugin.hpp>
|
||||
|
||||
#include <fc/api.hpp>
|
||||
#include <fc/optional.hpp>
|
||||
#include <fc/variant_object.hpp>
|
||||
|
|
@ -57,10 +59,47 @@
|
|||
namespace graphene { namespace app {
|
||||
|
||||
using namespace graphene::chain;
|
||||
using namespace graphene::market_history;
|
||||
using namespace std;
|
||||
|
||||
class database_api_impl;
|
||||
|
||||
struct order_book
|
||||
{
|
||||
string base;
|
||||
string quote;
|
||||
vector< pair<double,double> > bids;
|
||||
vector< pair<double,double> > asks;
|
||||
};
|
||||
|
||||
struct market_ticker
|
||||
{
|
||||
string base;
|
||||
string quote;
|
||||
double latest;
|
||||
double lowest_ask;
|
||||
double highest_bid;
|
||||
double percent_change;
|
||||
double base_volume;
|
||||
double quote_volume;
|
||||
};
|
||||
|
||||
struct market_volume
|
||||
{
|
||||
string base;
|
||||
string quote;
|
||||
double base_volume;
|
||||
double quote_volume;
|
||||
};
|
||||
|
||||
struct market_trade
|
||||
{
|
||||
fc::time_point_sec date;
|
||||
double price;
|
||||
double amount;
|
||||
double value;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The database_api class implements the RPC API for the chain database.
|
||||
*
|
||||
|
|
@ -324,7 +363,46 @@ class database_api
|
|||
* @param a First asset ID
|
||||
* @param b Second asset ID
|
||||
*/
|
||||
void unsubscribe_from_market(asset_id_type a, asset_id_type b);
|
||||
void unsubscribe_from_market( asset_id_type a, asset_id_type b );
|
||||
|
||||
/**
|
||||
* @brief Returns the ticker for the market assetA:assetB
|
||||
* @param a String name of the first asset
|
||||
* @param b String name of the second asset
|
||||
* @return The market ticker for the past 24 hours.
|
||||
*/
|
||||
market_ticker get_ticker( const string& base, const string& quote )const;
|
||||
|
||||
/**
|
||||
* @brief Returns the 24 hour volume for the market assetA:assetB
|
||||
* @param a String name of the first asset
|
||||
* @param b String name of the second asset
|
||||
* @return The market volume over the past 24 hours
|
||||
*/
|
||||
market_volume get_24_volume( const string& base, const string& quote )const;
|
||||
|
||||
/**
|
||||
* @brief Returns the order book for the market base:quote
|
||||
* @param base String name of the first asset
|
||||
* @param quote String name of the second asset
|
||||
* @param depth of the order book. Up to depth of each asks and bids, capped at 50. Prioritizes most moderate of each
|
||||
* @return Order book of the market
|
||||
*/
|
||||
order_book get_order_book( const string& base, const string& quote, unsigned limit = 50 )const;
|
||||
|
||||
/**
|
||||
* @brief Returns recent trades for the market assetA:assetB
|
||||
* Note: Currentlt, timezone offsets are not supported. The time must be UTC.
|
||||
* @param a String name of the first asset
|
||||
* @param b String name of the second asset
|
||||
* @param stop Stop time as a UNIX timestamp
|
||||
* @param limit Number of trasactions to retrieve, capped at 100
|
||||
* @param start Start time as a UNIX timestamp
|
||||
* @return Recent transactions in the market
|
||||
*/
|
||||
vector<market_trade> get_trade_history( const string& base, const string& quote, fc::time_point_sec start, fc::time_point_sec stop, unsigned limit = 100 )const;
|
||||
|
||||
|
||||
|
||||
///////////////
|
||||
// Witnesses //
|
||||
|
|
@ -475,6 +553,10 @@ class database_api
|
|||
};
|
||||
|
||||
} }
|
||||
FC_REFLECT( graphene::app::order_book, (base)(quote)(bids)(asks) );
|
||||
FC_REFLECT( graphene::app::market_ticker, (base)(quote)(latest)(lowest_ask)(highest_bid)(percent_change)(base_volume)(quote_volume) );
|
||||
FC_REFLECT( graphene::app::market_volume, (base)(quote)(base_volume)(quote_volume) );
|
||||
FC_REFLECT( graphene::app::market_trade, (date)(price)(amount)(value) );
|
||||
|
||||
FC_API(graphene::app::database_api,
|
||||
// Objects
|
||||
|
|
@ -524,12 +606,16 @@ FC_API(graphene::app::database_api,
|
|||
(lookup_asset_symbols)
|
||||
|
||||
// Markets / feeds
|
||||
(get_order_book)
|
||||
(get_limit_orders)
|
||||
(get_call_orders)
|
||||
(get_settle_orders)
|
||||
(get_margin_positions)
|
||||
(subscribe_to_market)
|
||||
(unsubscribe_from_market)
|
||||
(get_ticker)
|
||||
(get_24_volume)
|
||||
(get_trade_history)
|
||||
|
||||
// Witnesses
|
||||
(get_witnesses)
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ void database::clear_expired_orders()
|
|||
auto order_id = order.id;
|
||||
current_asset = order.settlement_asset_id();
|
||||
const asset_object& mia_object = get(current_asset);
|
||||
const asset_bitasset_data_object mia = mia_object.bitasset_data(*this);
|
||||
const asset_bitasset_data_object& mia = mia_object.bitasset_data(*this);
|
||||
|
||||
if( mia.has_settlement() )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -943,6 +943,7 @@ BOOST_AUTO_TEST_CASE( force_settle_test )
|
|||
// Wait for settlement to take effect
|
||||
generate_blocks(settle_id(db).settlement_date);
|
||||
BOOST_CHECK(db.find(settle_id) == nullptr);
|
||||
BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 50 );
|
||||
BOOST_CHECK_EQUAL( get_balance(nathan_id, bitusd_id), 14950);
|
||||
BOOST_CHECK_EQUAL( get_balance(nathan_id, core_id), 49 ); // 1% force_settlement_offset_percent (rounded unfavorably)
|
||||
BOOST_CHECK_EQUAL( call3_id(db).debt.value, 2950 );
|
||||
|
|
|
|||
Loading…
Reference in a new issue