Query the websocket to get the correct value for the ticker
This commit is contained in:
parent
1d61cc7f2f
commit
8f2a819ebe
7 changed files with 1954 additions and 44 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +1,6 @@
|
|||
[submodule "subprojects/wrenpp"]
|
||||
path = subprojects/wrenpp
|
||||
url = https://alarmpi.no-ip.org/gitan/King_DuckZ/wrenpp.git
|
||||
[submodule "subprojects/beast"]
|
||||
path = subprojects/beast
|
||||
url = https://github.com/boostorg/beast.git
|
||||
|
|
|
@ -8,6 +8,8 @@ project('duckticker', 'cpp',
|
|||
full_config_dir = get_option('prefix') / get_option('sysconfdir')
|
||||
compiler_opts = ['-DWRENPP_WITH_NAME_GUESSING']
|
||||
|
||||
beast_include = include_directories('subprojects/beast/include')
|
||||
|
||||
subdir('src')
|
||||
|
||||
#if meson.source_root() != full_config_dir
|
||||
|
|
1798
sample_responses.json
Normal file
1798
sample_responses.json
Normal file
File diff suppressed because it is too large
Load diff
184
src/main.cpp
184
src/main.cpp
|
@ -15,10 +15,12 @@
|
|||
* along with duckticker. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "nap/quick_rest.hpp"
|
||||
#include <boost/beast.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <simdjson.h>
|
||||
#include <wrenpp/vm_fun.hpp>
|
||||
#include <wrenpp/def_configuration.hpp>
|
||||
#include <wrenpp/callback_manager.hpp>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
@ -29,88 +31,178 @@ foreign class TickerPrice {
|
|||
|
||||
foreign price
|
||||
foreign currency
|
||||
foreign exchange
|
||||
foreign is_valid
|
||||
}
|
||||
|
||||
class Bitcoinity {
|
||||
foreign static ticker(currency)
|
||||
foreign static ticker(currency, exchange)
|
||||
}
|
||||
|
||||
class App {
|
||||
construct new(currency) {
|
||||
construct new(currency, exchange) {
|
||||
_currency = currency
|
||||
_exchange = exchange
|
||||
}
|
||||
|
||||
start() {
|
||||
var price = Bitcoinity.ticker(_currency)
|
||||
System.print("Ticker: %(price.price) %(price.currency)")
|
||||
var price = Bitcoinity.ticker(_currency, _exchange)
|
||||
System.print("Ticker: %(price.price) %(price.currency) on %(price.exchange)")
|
||||
}
|
||||
}
|
||||
|
||||
var the_app = App.new("USD")
|
||||
var the_app = App.new("USD", "kraken")
|
||||
)script";
|
||||
|
||||
class WebsocketReader {
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
public:
|
||||
WebsocketReader (std::string host,
|
||||
const std::string& port,
|
||||
const std::string& path,
|
||||
const std::string& user_agent,
|
||||
const std::string& message
|
||||
);
|
||||
|
||||
~WebsocketReader();
|
||||
|
||||
std::string read();
|
||||
|
||||
private:
|
||||
boost::asio::io_context m_ioc;
|
||||
tcp::resolver m_resolver;
|
||||
boost::beast::websocket::stream<tcp::socket> m_ws;
|
||||
};
|
||||
|
||||
WebsocketReader::WebsocketReader (std::string host,
|
||||
const std::string& port,
|
||||
const std::string& path,
|
||||
const std::string& user_agent,
|
||||
const std::string& message
|
||||
) :
|
||||
m_resolver{m_ioc},
|
||||
m_ws{m_ioc}
|
||||
{
|
||||
//see https://www.boost.org/doc/libs/develop/libs/beast/example/websocket/client/sync/websocket_client_sync.cpp
|
||||
const auto results = m_resolver.resolve(host, port);
|
||||
auto ep = boost::asio::connect(m_ws.next_layer(), results);
|
||||
host += ':' + std::to_string(ep.port());
|
||||
m_ws.set_option(boost::beast::websocket::stream_base::decorator(
|
||||
[user_agent](boost::beast::websocket::request_type& req)
|
||||
{
|
||||
req.set(boost::beast::http::field::user_agent, user_agent);
|
||||
}
|
||||
));
|
||||
m_ws.handshake(host, path);
|
||||
m_ws.write(boost::asio::buffer(message));
|
||||
}
|
||||
|
||||
std::string WebsocketReader::read() {
|
||||
boost::beast::flat_buffer buffer;
|
||||
m_ws.read(buffer);
|
||||
return boost::beast::buffers_to_string(buffer.data());
|
||||
}
|
||||
|
||||
WebsocketReader::~WebsocketReader() {
|
||||
m_ws.close(boost::beast::websocket::close_code::normal);
|
||||
}
|
||||
|
||||
class TickerPrice {
|
||||
public:
|
||||
TickerPrice() = default;
|
||||
TickerPrice (double price, std::string&& currency) :
|
||||
TickerPrice (double price, std::string&& currency, std::string&& exchange) :
|
||||
m_price(price),
|
||||
m_currency(std::move(currency))
|
||||
m_currency(std::move(currency)),
|
||||
m_exchange(std::move(exchange))
|
||||
{}
|
||||
|
||||
void set_price (double price) { m_price = price; }
|
||||
void set_currency (std::string&& currency) { m_currency = std::move(currency); }
|
||||
void set_exchange (std::string&& exchange) { m_exchange = std::move(exchange); }
|
||||
double price() const noexcept { return m_price; }
|
||||
const char* currency() const noexcept { return m_currency.c_str(); }
|
||||
const char* exchange() const noexcept { return m_exchange.c_str(); }
|
||||
bool is_valid() const noexcept { return not m_exchange.empty(); }
|
||||
|
||||
private:
|
||||
double m_price{};
|
||||
std::string m_currency;
|
||||
std::string m_exchange;
|
||||
};
|
||||
|
||||
TickerPrice ticker_price (std::string_view currency) {
|
||||
nap::QuickRest qrest{simdjson::SIMDJSON_PADDING};
|
||||
qrest.set_user_agent("duckticker");
|
||||
qrest.add_post("currency", currency);
|
||||
TickerPrice ticker_price_bitoinity (std::string_view currency, std::string_view exchange) {
|
||||
static const TickerPrice error_price {0.0, "", ""};
|
||||
|
||||
auto response = qrest.fetch("https://bitcoinity.org/simple_switch_currency");
|
||||
//std::cout << response.body << '\n';
|
||||
WebsocketReader websocket{"bitcoinity.org",
|
||||
"80",
|
||||
"/webs_bridge/websocket",
|
||||
std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-coro",
|
||||
R"({"topic":"webs:markets_)" + std::string{exchange} + "_" +
|
||||
std::string{currency} + R"(","event":"phx_join","payload":{},"ref":"3"})"
|
||||
};
|
||||
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::padded_string_view body{response.body, response.body.size() + simdjson::SIMDJSON_PADDING};
|
||||
simdjson::ondemand::document doc = parser.iterate(body);
|
||||
const double price_out = doc["price"];
|
||||
const std::string_view currency_out = doc["currency"];
|
||||
return {price_out, std::string{currency_out}};
|
||||
{
|
||||
simdjson::padded_string body = websocket.read();
|
||||
std::cout << body << '\n';
|
||||
if (body.size() == 0)
|
||||
return error_price;
|
||||
simdjson::ondemand::document doc = parser.iterate(body);
|
||||
|
||||
if (doc["event"].get_string().value() != "phx_reply" or doc["payload"]["status"].get_string().value() != "ok")
|
||||
return error_price;
|
||||
}
|
||||
|
||||
{
|
||||
double price_out;
|
||||
std::string currency_out;
|
||||
std::string exchange_out;
|
||||
do {
|
||||
simdjson::padded_string body = websocket.read();
|
||||
std::cout << body << '\n';
|
||||
simdjson::ondemand::document doc = parser.iterate(body);
|
||||
auto payload_result = doc["payload"];
|
||||
if (payload_result.error())
|
||||
return error_price;
|
||||
simdjson::ondemand::object payload = payload_result;
|
||||
auto data_result = payload["data"];
|
||||
if (data_result.error())
|
||||
return error_price;
|
||||
simdjson::ondemand::object data = data_result;
|
||||
|
||||
simdjson::error_code price_error;
|
||||
if (not data["depth"].error()) {
|
||||
price_error = data["depth"]["price"].get(price_out);
|
||||
}
|
||||
else if (not data["trade"].error()) {
|
||||
price_error = data["trade"]["price"].get(price_out);
|
||||
}
|
||||
else if (not data["depth_shot"].error()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string_view currency_out_view, exchange_out_view;
|
||||
const auto currency_error = data["currency"].get(currency_out_view);
|
||||
const auto exchange_error = data["exchange_name"].get(exchange_out_view);
|
||||
|
||||
if (price_error or currency_error or exchange_error)
|
||||
return error_price;
|
||||
|
||||
currency_out = currency_out_view;
|
||||
exchange_out = exchange_out_view;
|
||||
} while (false);
|
||||
|
||||
return {price_out, std::move(currency_out), std::move(exchange_out)};
|
||||
}
|
||||
}
|
||||
|
||||
void ticker_price_wren (wren::VM& vm) {
|
||||
void ticker_price_bitcoinity_wren (wren::VM& vm) {
|
||||
TickerPrice* const tp = wren::make_foreign_object<TickerPrice>(vm, "main");
|
||||
*tp = ticker_price(wren::get<const char*>(vm, 1));
|
||||
*tp = ticker_price_bitoinity(wren::get<const char*>(vm, 1), wren::get<const char*>(vm, 2));
|
||||
}
|
||||
|
||||
class MyWrenConfiguration : public wren::DefConfiguration {
|
||||
public:
|
||||
wren::foreign_method_t foreign_method_fn(
|
||||
wren::VM* vm,
|
||||
std::string_view module,
|
||||
std::string_view class_name,
|
||||
bool is_static,
|
||||
std::string_view signature
|
||||
) {
|
||||
if ("main" == module) {
|
||||
if (is_static and "Bitcoinity" == class_name and "ticker(_)" == signature) {
|
||||
return &ticker_price_wren;
|
||||
}
|
||||
else if ("TickerPrice" == class_name) {
|
||||
if (not is_static and "price" == signature)
|
||||
return wren::make_method_bindable<&TickerPrice::price>();
|
||||
else if (not is_static and "currency" == signature)
|
||||
return wren::make_method_bindable<&TickerPrice::currency>();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
wren::foreign_class_t foreign_class_fn(
|
||||
wren::VM* vm,
|
||||
std::string_view module,
|
||||
|
@ -127,8 +219,14 @@ public:
|
|||
int main() {
|
||||
MyWrenConfiguration config;
|
||||
wren::VM vm(&config, nullptr);
|
||||
vm.interpret("main", g_script);
|
||||
vm.callback_manager()
|
||||
.add_callback(true, "main", "Bitcoinity", "ticker(_,_)", &ticker_price_bitcoinity_wren)
|
||||
.add_callback(false, "main", "TickerPrice", "price", wren::make_method_bindable<&TickerPrice::price>())
|
||||
.add_callback(false, "main", "TickerPrice", "currency", wren::make_method_bindable<&TickerPrice::currency>())
|
||||
.add_callback(false, "main", "TickerPrice", "is_valid", wren::make_method_bindable<&TickerPrice::is_valid>())
|
||||
.add_callback(false, "main", "TickerPrice", "exchange", wren::make_method_bindable<&TickerPrice::exchange>());
|
||||
|
||||
vm.interpret("main", g_script);
|
||||
wren::call<void>(vm, {"main", "the_app"}, "start");
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -10,6 +10,10 @@ wrenpp_dep = dependency('wrenpp', version: '>=0.1.1',
|
|||
default_options: ['wren_with_rand=true'],
|
||||
)
|
||||
|
||||
boost_dep = dependency('boost', version: '>=1.78.0',
|
||||
modules: ['coroutine', 'filesystem', 'system', 'thread'],
|
||||
)
|
||||
|
||||
simdjson_dep = dependency('simdjson', version: '>=0.5.0',
|
||||
fallback: ['simdjson', 'simdjson_dep'],
|
||||
)
|
||||
|
@ -24,6 +28,10 @@ executable(meson.project_name(),
|
|||
curlcpp_dep,
|
||||
wrenpp_dep,
|
||||
simdjson_dep,
|
||||
boost_dep,
|
||||
],
|
||||
cpp_args: compiler_opts,
|
||||
include_directories: [
|
||||
beast_include,
|
||||
],
|
||||
)
|
||||
|
|
1
subprojects/beast
Submodule
1
subprojects/beast
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 9d23bec2bc523223a377582302bd28d04cb514ed
|
|
@ -1 +1 @@
|
|||
Subproject commit edd5f27ab2013e7d768fead766274b1a9eab6075
|
||||
Subproject commit d985ebc417f80c59fe26200a9e1e677f54ccb9e9
|
Loading…
Reference in a new issue