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"]
|
[submodule "subprojects/wrenpp"]
|
||||||
path = subprojects/wrenpp
|
path = subprojects/wrenpp
|
||||||
url = https://alarmpi.no-ip.org/gitan/King_DuckZ/wrenpp.git
|
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')
|
full_config_dir = get_option('prefix') / get_option('sysconfdir')
|
||||||
compiler_opts = ['-DWRENPP_WITH_NAME_GUESSING']
|
compiler_opts = ['-DWRENPP_WITH_NAME_GUESSING']
|
||||||
|
|
||||||
|
beast_include = include_directories('subprojects/beast/include')
|
||||||
|
|
||||||
subdir('src')
|
subdir('src')
|
||||||
|
|
||||||
#if meson.source_root() != full_config_dir
|
#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
182
src/main.cpp
182
src/main.cpp
|
@ -15,10 +15,12 @@
|
||||||
* along with duckticker. If not, see <http://www.gnu.org/licenses/>.
|
* 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 <simdjson.h>
|
||||||
#include <wrenpp/vm_fun.hpp>
|
#include <wrenpp/vm_fun.hpp>
|
||||||
#include <wrenpp/def_configuration.hpp>
|
#include <wrenpp/def_configuration.hpp>
|
||||||
|
#include <wrenpp/callback_manager.hpp>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -29,88 +31,178 @@ foreign class TickerPrice {
|
||||||
|
|
||||||
foreign price
|
foreign price
|
||||||
foreign currency
|
foreign currency
|
||||||
|
foreign exchange
|
||||||
|
foreign is_valid
|
||||||
}
|
}
|
||||||
|
|
||||||
class Bitcoinity {
|
class Bitcoinity {
|
||||||
foreign static ticker(currency)
|
foreign static ticker(currency, exchange)
|
||||||
}
|
}
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
construct new(currency) {
|
construct new(currency, exchange) {
|
||||||
_currency = currency
|
_currency = currency
|
||||||
|
_exchange = exchange
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
var price = Bitcoinity.ticker(_currency)
|
var price = Bitcoinity.ticker(_currency, _exchange)
|
||||||
System.print("Ticker: %(price.price) %(price.currency)")
|
System.print("Ticker: %(price.price) %(price.currency) on %(price.exchange)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var the_app = App.new("USD")
|
var the_app = App.new("USD", "kraken")
|
||||||
)script";
|
)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 {
|
class TickerPrice {
|
||||||
public:
|
public:
|
||||||
TickerPrice() = default;
|
TickerPrice() = default;
|
||||||
TickerPrice (double price, std::string&& currency) :
|
TickerPrice (double price, std::string&& currency, std::string&& exchange) :
|
||||||
m_price(price),
|
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_price (double price) { m_price = price; }
|
||||||
void set_currency (std::string&& currency) { m_currency = std::move(currency); }
|
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; }
|
double price() const noexcept { return m_price; }
|
||||||
const char* currency() const noexcept { return m_currency.c_str(); }
|
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:
|
private:
|
||||||
double m_price{};
|
double m_price{};
|
||||||
std::string m_currency;
|
std::string m_currency;
|
||||||
|
std::string m_exchange;
|
||||||
};
|
};
|
||||||
|
|
||||||
TickerPrice ticker_price (std::string_view currency) {
|
TickerPrice ticker_price_bitoinity (std::string_view currency, std::string_view exchange) {
|
||||||
nap::QuickRest qrest{simdjson::SIMDJSON_PADDING};
|
static const TickerPrice error_price {0.0, "", ""};
|
||||||
qrest.set_user_agent("duckticker");
|
|
||||||
qrest.add_post("currency", currency);
|
|
||||||
|
|
||||||
auto response = qrest.fetch("https://bitcoinity.org/simple_switch_currency");
|
WebsocketReader websocket{"bitcoinity.org",
|
||||||
//std::cout << response.body << '\n';
|
"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::ondemand::parser parser;
|
||||||
simdjson::padded_string_view body{response.body, response.body.size() + simdjson::SIMDJSON_PADDING};
|
{
|
||||||
|
simdjson::padded_string body = websocket.read();
|
||||||
|
std::cout << body << '\n';
|
||||||
|
if (body.size() == 0)
|
||||||
|
return error_price;
|
||||||
simdjson::ondemand::document doc = parser.iterate(body);
|
simdjson::ondemand::document doc = parser.iterate(body);
|
||||||
const double price_out = doc["price"];
|
|
||||||
const std::string_view currency_out = doc["currency"];
|
if (doc["event"].get_string().value() != "phx_reply" or doc["payload"]["status"].get_string().value() != "ok")
|
||||||
return {price_out, std::string{currency_out}};
|
return error_price;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ticker_price_wren (wren::VM& vm) {
|
{
|
||||||
|
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_bitcoinity_wren (wren::VM& vm) {
|
||||||
TickerPrice* const tp = wren::make_foreign_object<TickerPrice>(vm, "main");
|
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 {
|
class MyWrenConfiguration : public wren::DefConfiguration {
|
||||||
public:
|
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::foreign_class_t foreign_class_fn(
|
||||||
wren::VM* vm,
|
wren::VM* vm,
|
||||||
std::string_view module,
|
std::string_view module,
|
||||||
|
@ -127,8 +219,14 @@ public:
|
||||||
int main() {
|
int main() {
|
||||||
MyWrenConfiguration config;
|
MyWrenConfiguration config;
|
||||||
wren::VM vm(&config, nullptr);
|
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");
|
wren::call<void>(vm, {"main", "the_app"}, "start");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -10,6 +10,10 @@ wrenpp_dep = dependency('wrenpp', version: '>=0.1.1',
|
||||||
default_options: ['wren_with_rand=true'],
|
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',
|
simdjson_dep = dependency('simdjson', version: '>=0.5.0',
|
||||||
fallback: ['simdjson', 'simdjson_dep'],
|
fallback: ['simdjson', 'simdjson_dep'],
|
||||||
)
|
)
|
||||||
|
@ -24,6 +28,10 @@ executable(meson.project_name(),
|
||||||
curlcpp_dep,
|
curlcpp_dep,
|
||||||
wrenpp_dep,
|
wrenpp_dep,
|
||||||
simdjson_dep,
|
simdjson_dep,
|
||||||
|
boost_dep,
|
||||||
],
|
],
|
||||||
cpp_args: compiler_opts,
|
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