Issue #47 - Basic, Untested, Market History Plugin

This commit is contained in:
Daniel Larimer 2015-06-23 17:53:10 -04:00
parent 0d02361af0
commit fdefc69baf
6 changed files with 342 additions and 2 deletions

View file

@ -104,9 +104,9 @@ class plugin : public abstract_plugin
bpo::options_description& config_file_options
) override;
protected:
chain::database& database() { return *app().chain_database(); }
application& app()const { assert(_app); return *_app; }
protected:
net::node& p2p_node() { return *app().p2p_node(); }
private:

View file

@ -1,2 +1,3 @@
add_subdirectory( witness )
add_subdirectory( account_history )
add_subdirectory( market_history )

View file

@ -42,7 +42,8 @@ namespace bpo = boost::program_options;
enum account_history_object_type
{
key_account_object_type
key_account_object_type = 0,
bucket_object_type = 1 ///< used in market_history_plugin
};
class key_account_object : public abstract_object<key_account_object>

View file

@ -0,0 +1,13 @@
file(GLOB HEADERS "include/graphene/market_history/*.hpp")
add_library( graphene_market_history
market_history_plugin.cpp
)
target_link_libraries( graphene_market_history graphene_chain graphene_app )
target_include_directories( graphene_market_history
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" )
if(MSVC)
set_source_files_properties( market_history_plugin.cpp PROPERTIES COMPILE_FLAGS "/bigobj" )
endif(MSVC)

View file

@ -0,0 +1,131 @@
/*
* Copyright (c) 2015, Cryptonomex, Inc.
* All rights reserved.
*
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
* are permitted until September 8, 2015, provided that the following conditions are met:
*
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <graphene/app/plugin.hpp>
#include <graphene/chain/database.hpp>
#include <fc/thread/future.hpp>
namespace graphene { namespace market_history {
using namespace chain;
namespace bpo = boost::program_options;
//
// Plugins should #define their SPACE_ID's so plugins with
// conflicting SPACE_ID assignments can be compiled into the
// same binary (by simply re-assigning some of the conflicting #defined
// SPACE_ID's in a build script).
//
// Assignment of SPACE_ID's cannot be done at run-time because
// various template automagic depends on them being known at compile
// time.
//
#ifndef ACCOUNT_HISTORY_SPACE_ID
#define ACCOUNT_HISTORY_SPACE_ID 5
#endif
struct bucket_key
{
asset_id_type base;
asset_id_type quote;
uint32_t seconds = 0;
fc::time_point_sec open;
friend bool operator < ( const bucket_key& a, const bucket_key& b )
{
return std::tie( a.base, a.quote, b.seconds, a.open ) < std::tie( b.base, b.quote, b.seconds, b.open );
}
friend bool operator == ( const bucket_key& a, const bucket_key& b )
{
return std::tie( a.base, a.quote, b.seconds, a.open ) == std::tie( b.base, b.quote, b.seconds, b.open );
}
};
struct bucket_object : public abstract_object<bucket_object>
{
static const uint8_t space_id = ACCOUNT_HISTORY_SPACE_ID;
static const uint8_t type_id = 1; // market_history_plugin type, referenced from account_history_plugin.hpp
price high()const { return asset( high_base, key.base ) / asset( high_quote, key.quote ); }
price low()const { return asset( low_base, key.base ) / asset( low_quote, key.quote ); }
bucket_key key;
share_type high_base;
share_type high_quote;
share_type low_base;
share_type low_quote;
share_type open_base;
share_type open_quote;
share_type close_base;
share_type close_quote;
share_type base_volume;
share_type quote_volume;
};
struct by_key;
typedef multi_index_container<
bucket_object,
indexed_by<
hashed_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_unique< tag<by_key>, member< bucket_object, bucket_key, &bucket_object::key > >
>
> bucket_object_multi_index_type;
typedef generic_index<bucket_object, bucket_object_multi_index_type> bucket_index;
namespace detail
{
class market_history_plugin_impl;
}
/**
* The market history plugin can be configured to track any number of intervals via its configuration. Once per block it
* will scan the virtual operations and look for fill_order_operations and then adjust the appropriate bucket objects for
* each fill order.
*/
class market_history_plugin : public graphene::app::plugin
{
public:
market_history_plugin();
virtual ~market_history_plugin();
std::string plugin_name()const override;
virtual void plugin_set_program_options(bpo::options_description& cli, bpo::options_description& cfg) override;
virtual void plugin_initialize(const bpo::variables_map& options) override;
virtual void plugin_startup() override;
vector<bucket_object> get_history( const bucket_key& start, const bucket_key& end )const;
const flat_set<uint32_t>& tracked_buckets()const;
private:
friend class detail::market_history_plugin_impl;
std::unique_ptr<detail::market_history_plugin_impl> my;
};
} } //graphene::market_history
FC_REFLECT( graphene::market_history::bucket_key, (base)(quote)(seconds)(open) )
FC_REFLECT( graphene::market_history::bucket_object, (key)
(high_base)(high_quote)
(low_base)(low_quote)
(open_base)(open_quote)
(close_base)(close_quote)
(base_volume)(quote_volume) )

View file

@ -0,0 +1,194 @@
/*
* Copyright (c) 2015, Cryptonomex, Inc.
* All rights reserved.
*
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
* are permitted until September 8, 2015, provided that the following conditions are met:
*
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <graphene/market_history/market_history_plugin.hpp>
#include <graphene/chain/account_evaluator.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/config.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/key_object.hpp>
#include <graphene/chain/operation_history_object.hpp>
#include <graphene/chain/transaction_evaluation_state.hpp>
#include <fc/thread/thread.hpp>
namespace graphene { namespace market_history {
namespace detail
{
class market_history_plugin_impl
{
public:
market_history_plugin_impl(market_history_plugin& _plugin)
:_self( _plugin ) {}
virtual ~market_history_plugin_impl();
/** this method is called as a callback after a block is applied
* and will process/index all operations that were applied in the block.
*/
void update_market_histories( const signed_block& b );
graphene::chain::database& database()
{
return _self.database();
}
market_history_plugin& _self;
flat_set<uint32_t> _tracked_buckets;
};
struct operation_process_fill_order
{
market_history_plugin& _plugin;
fc::time_point_sec _now;
operation_process_fill_order( market_history_plugin& mhp, fc::time_point_sec n )
:_plugin(mhp),_now(n) {}
typedef void result_type;
/** do nothing for other operation types */
template<typename T>
void operator()( const T& )const{}
void operator()( const fill_order_operation& o )const
{
const auto& buckets = _plugin.tracked_buckets();
auto& db = _plugin.database();
const auto& bucket_idx = db.get_index_type<bucket_index>();
for( auto bucket : buckets )
{
bucket_key key;
key.base = o.pays.asset_id;
key.quote = o.receives.asset_id;
price trade_price = o.pays / o.receives;
if( key.base > key.quote )
{
std::swap( key.base,key.quote );
trade_price = ~trade_price;
}
key.seconds = bucket;
key.open = fc::time_point() + fc::seconds((_now.sec_since_epoch() / key.seconds) * key.seconds);
const auto& by_key_idx = bucket_idx.indices().get<by_key>();
auto itr = by_key_idx.find( key );
if( itr == by_key_idx.end() )
{ // create new bucket
db.create<bucket_object>( [&]( bucket_object& b ){
b.key = key;
b.quote_volume += trade_price.quote.amount;
b.open_base = trade_price.base.amount;
b.open_quote = trade_price.quote.amount;
b.close_base = trade_price.base.amount;
b.close_quote = trade_price.quote.amount;
b.high_base = b.close_base;
b.high_quote = b.close_quote;
b.low_base = b.close_base;
b.low_quote = b.close_quote;
});
}
else
{ // update existing bucket
db.modify( *itr, [&]( bucket_object& b ){
b.base_volume += trade_price.base.amount;
b.quote_volume += trade_price.quote.amount;
b.close_base = trade_price.base.amount;
b.close_quote = trade_price.quote.amount;
if( b.high() < trade_price )
{
b.high_base = b.close_base;
b.high_quote = b.close_quote;
}
if( b.low() > trade_price )
{
b.low_base = b.close_base;
b.low_quote = b.close_quote;
}
});
}
}
}
};
market_history_plugin_impl::~market_history_plugin_impl()
{}
void market_history_plugin_impl::update_market_histories( const signed_block& b )
{
graphene::chain::database& db = database();
const vector<operation_history_object>& hist = db.get_applied_operations();
for( auto op : hist )
op.op.visit( operation_process_fill_order( _self, b.timestamp ) );
}
} // end namespace detail
market_history_plugin::market_history_plugin() :
my( new detail::market_history_plugin_impl(*this) )
{
}
market_history_plugin::~market_history_plugin()
{
}
std::string market_history_plugin::plugin_name()const
{
return "market_history";
}
void market_history_plugin::plugin_set_program_options(
boost::program_options::options_description& cli,
boost::program_options::options_description& cfg
)
{
cli.add_options()
("bucket-size", bpo::value<std::vector<uint32_t>>()->composing()->multitoken(), "Bucket size in seconds to track history for (may specify multiple times)")
;
cfg.add(cli);
}
void market_history_plugin::plugin_initialize(const boost::program_options::variables_map& options)
{
database().applied_block.connect( [&]( const signed_block& b){ my->update_market_histories(b); } );
database().add_index< primary_index< bucket_index > >();
LOAD_VALUE_SET(options, "bucket-size", my->_tracked_buckets, uint32_t);
}
void market_history_plugin::plugin_startup()
{
}
const flat_set<uint32_t>& market_history_plugin::tracked_buckets() const
{
return my->_tracked_buckets;
}
} }