Query the websocket to get the correct value for the ticker

This commit is contained in:
King_DuckZ 2022-05-13 23:11:01 +02:00
parent 1d61cc7f2f
commit 8f2a819ebe
7 changed files with 1954 additions and 44 deletions

3
.gitmodules vendored
View file

@ -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

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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;

View file

@ -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

@ -0,0 +1 @@
Subproject commit 9d23bec2bc523223a377582302bd28d04cb514ed

@ -1 +1 @@
Subproject commit edd5f27ab2013e7d768fead766274b1a9eab6075
Subproject commit d985ebc417f80c59fe26200a9e1e677f54ccb9e9