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
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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue