move C++ SDK out of Go repo — luxcpp/dex is canonical

Deletes orphaned C++ duplicates from the dex Go repo:
  sdk/cpp/         (→ luxcpp/dex/sdk/cpp/)
  sdk/lx-trading-c/  (→ luxcpp/dex/sdk/trading-c/)
  sdk/lx-trading-cpp/(→ luxcpp/dex/sdk/trading-cpp/)
  client/cpp/      (→ luxcpp/dex/client/cpp/)

Confirmed orphaned: the live CGO consumers (pkg/lx/amm_gpu.go,
pkg/lx/signed_order_gpu.go, pkg/lx/signed_order_metal_anchor_darwin.go)
already point their CFLAGS/LDFLAGS at ~/work/luxcpp/dex/build and
~/work/luxcpp/crypto/build. None reference the deleted trees.

luxcpp/dex is 5 months newer (2026-05-05 vs 2025-12-11), so deletion
preserves the canonical version. pkg/orderbook/cpp_orderbook.go's
inline placeholder C++ left untouched — it's a stub, no external link.
This commit is contained in:
2026-05-21 13:57:16 -07:00
parent 14a8590f22
commit 09c17ec533
62 changed files with 0 additions and 17569 deletions
-92
View File
@@ -1,92 +0,0 @@
# LX C++ CLI Trading Client
# Copyright (c) 2025 Lux Partners Limited
# SPDX-License-Identifier: MIT
cmake_minimum_required(VERSION 3.16)
project(lx-cli VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Release with debug info by default for profiling
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()
find_package(Threads REQUIRED)
include(FetchContent)
# nlohmann/json - JSON parsing
FetchContent_Declare(
json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(json)
# websocketpp - WebSocket client
FetchContent_Declare(
websocketpp
GIT_REPOSITORY https://github.com/zaphoyd/websocketpp.git
GIT_TAG 0.8.2
)
FetchContent_Populate(websocketpp)
# asio standalone (no Boost)
FetchContent_Declare(
asio
GIT_REPOSITORY https://github.com/chriskohlhoff/asio.git
GIT_TAG asio-1-28-2
)
FetchContent_Populate(asio)
# CLI executable
add_executable(lx-cli
src/main.cpp
)
target_include_directories(lx-cli
PRIVATE
${websocketpp_SOURCE_DIR}
${asio_SOURCE_DIR}/asio/include
)
target_link_libraries(lx-cli
PRIVATE
nlohmann_json::nlohmann_json
Threads::Threads
)
target_compile_definitions(lx-cli
PRIVATE
ASIO_STANDALONE
_WEBSOCKETPP_CPP11_STL_
)
# Platform-specific
if(APPLE)
target_compile_definitions(lx-cli PRIVATE _DARWIN_C_SOURCE)
endif()
if(WIN32)
target_link_libraries(lx-cli PRIVATE ws2_32)
endif()
# Optimization flags for HFT
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(lx-cli PRIVATE
-O3
-march=native
-Wall
-Wextra
-Wpedantic
)
endif()
# Install
install(TARGETS lx-cli
RUNTIME DESTINATION bin
)
-142
View File
@@ -1,142 +0,0 @@
# LX C++ CLI Trading Client
Command-line trading interface for LX WebSocket API.
## Build
```bash
mkdir build && cd build
cmake ..
make -j$(nproc)
```
## Usage
```bash
# Interactive mode (default)
./lx-cli
# Interactive mode with authentication
./lx-cli -k YOUR_API_KEY -s YOUR_API_SECRET
# Single command mode
./lx-cli place_order BTC-USD buy limit 50000 0.1
./lx-cli cancel_order 12345
./lx-cli get_orderbook BTC-USD
./lx-cli get_balances
./lx-cli ping
# Custom server
./lx-cli -u ws://trading.example.com:8081 get_positions
# Verbose output
./lx-cli -v ping
```
## Options
| Option | Description |
|--------|-------------|
| `-u, --url <url>` | WebSocket server URL (default: ws://localhost:8081) |
| `-k, --key <key>` | API key for authentication |
| `-s, --secret <secret>` | API secret for authentication |
| `-i, --interactive` | Interactive REPL mode |
| `-v, --verbose` | Show raw JSON messages |
| `-h, --help` | Show help |
## Commands
### Trading
```
place_order <symbol> <side> <type> <price> <size>
Place a new order
Example: place_order BTC-USD buy limit 50000 0.1
cancel_order <order_id>
Cancel an existing order
Example: cancel_order 12345
```
### Market Data
```
get_orderbook <symbol>
Get orderbook snapshot
Example: get_orderbook BTC-USD
subscribe <symbol>
Subscribe to orderbook updates
Example: subscribe ETH-USD
```
### Account
```
get_positions
Show all open positions
get_orders
Show all open orders
get_balances
Show account balances
```
### Utility
```
ping
Test connection latency
help
Show available commands
quit / exit
Exit the CLI
```
## Interactive Example
```
$ ./lx-cli -k mykey -s mysecret
LX CLI - Type 'help' for commands
> ping
Pong: 1234 us
> place_order BTC-USD buy limit 50000 0.1
{
"type": "order_placed",
"data": {
"orderId": 12345,
"status": "open"
}
}
> get_orders
{
"type": "orders",
"data": {
"orders": [...]
}
}
> quit
Goodbye
```
## Dependencies
Fetched automatically via CMake FetchContent:
- [nlohmann/json](https://github.com/nlohmann/json) - JSON parsing
- [websocketpp](https://github.com/zaphoyd/websocketpp) - WebSocket client
- [asio](https://github.com/chriskohlhoff/asio) - Async I/O (standalone, no Boost)
## Performance
Optimized for HFT:
- Native compilation with `-march=native`
- Lock-free message queue
- Microsecond-precision latency measurement
- Minimal allocations in hot path
## License
MIT License - Copyright (c) 2025 Lux Partners Limited
-813
View File
@@ -1,813 +0,0 @@
// LX C++ CLI Trading Client
// Copyright (c) 2025 Lux Partners Limited
// SPDX-License-Identifier: MIT
//
// Command-line trading interface for LX WebSocket API.
// Connect to ws://localhost:8081 for real-time trading.
#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/client.hpp>
#include <nlohmann/json.hpp>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <queue>
#include <sstream>
#include <string>
#include <thread>
#include <unordered_map>
using json = nlohmann::json;
using WsClient = websocketpp::client<websocketpp::config::asio_client>;
using ConnectionHdl = websocketpp::connection_hdl;
using MessagePtr = websocketpp::config::asio_client::message_type::ptr;
//------------------------------------------------------------------------------
// Configuration
//------------------------------------------------------------------------------
struct Config {
std::string ws_url = "ws://localhost:8081";
std::string api_key;
std::string api_secret;
bool verbose = false;
bool interactive = true;
std::vector<std::string> command_args;
};
//------------------------------------------------------------------------------
// WebSocket Client
//------------------------------------------------------------------------------
class Client {
public:
explicit Client(const Config& config)
: config_(config)
, connected_(false)
, authenticated_(false)
, request_counter_(0)
, running_(false)
{
ws_.clear_access_channels(websocketpp::log::alevel::all);
ws_.clear_error_channels(websocketpp::log::elevel::all);
ws_.init_asio();
ws_.set_open_handler([this](ConnectionHdl) {
std::lock_guard<std::mutex> lock(connect_mutex_);
connected_ = true;
connect_cv_.notify_all();
});
ws_.set_close_handler([this](ConnectionHdl) {
std::lock_guard<std::mutex> lock(connect_mutex_);
connected_ = false;
connect_cv_.notify_all();
});
ws_.set_fail_handler([this](ConnectionHdl) {
std::lock_guard<std::mutex> lock(connect_mutex_);
connected_ = false;
connect_cv_.notify_all();
});
ws_.set_message_handler([this](ConnectionHdl, MessagePtr msg) {
on_message(msg);
});
}
~Client() {
disconnect();
}
bool connect() {
websocketpp::lib::error_code ec;
auto con = ws_.get_connection(config_.ws_url, ec);
if (ec) {
std::cerr << "Connection error: " << ec.message() << "\n";
return false;
}
connection_ = con->get_handle();
ws_.connect(con);
running_ = true;
io_thread_ = std::thread([this]() { ws_.run(); });
// Wait for connection
{
std::unique_lock<std::mutex> lock(connect_mutex_);
bool ok = connect_cv_.wait_for(lock, std::chrono::seconds(10), [this]() {
return connected_.load();
});
if (!ok) {
std::cerr << "Connection timeout\n";
return false;
}
}
// Wait for connected message from server
auto resp = wait_response("", std::chrono::seconds(5));
if (resp && resp->contains("type") && (*resp)["type"] == "connected") {
if (config_.verbose) {
std::cout << "Connected to LX\n";
}
}
return true;
}
void disconnect() {
if (!running_) return;
running_ = false;
try {
if (!connection_.expired()) {
ws_.close(connection_, websocketpp::close::status::normal, "");
}
} catch (...) {}
ws_.stop();
if (io_thread_.joinable()) {
io_thread_.join();
}
}
bool is_connected() const { return connected_.load(); }
bool authenticate() {
if (config_.api_key.empty() || config_.api_secret.empty()) {
return true; // No auth required
}
json msg = {
{"type", "auth"},
{"apiKey", config_.api_key},
{"apiSecret", config_.api_secret},
{"request_id", next_request_id()}
};
auto resp = send_and_wait(msg);
if (!resp) {
std::cerr << "Auth timeout\n";
return false;
}
if (resp->contains("error") && !(*resp)["error"].is_null()) {
std::cerr << "Auth failed: " << (*resp)["error"] << "\n";
return false;
}
authenticated_ = true;
return true;
}
// Place order
std::optional<json> place_order(const std::string& symbol, const std::string& side,
const std::string& type, double price, double size) {
json order = {
{"symbol", symbol},
{"side", side},
{"type", type},
{"price", price},
{"size", size}
};
json msg = {
{"type", "place_order"},
{"order", order},
{"request_id", next_request_id()}
};
return send_and_wait(msg);
}
// Cancel order
std::optional<json> cancel_order(uint64_t order_id) {
json msg = {
{"type", "cancel_order"},
{"orderID", order_id},
{"request_id", next_request_id()}
};
return send_and_wait(msg);
}
// Get positions
std::optional<json> get_positions() {
json msg = {
{"type", "get_positions"},
{"request_id", next_request_id()}
};
return send_and_wait(msg);
}
// Get open orders
std::optional<json> get_orders() {
json msg = {
{"type", "get_orders"},
{"request_id", next_request_id()}
};
return send_and_wait(msg);
}
// Get balances
std::optional<json> get_balances() {
json msg = {
{"type", "get_balances"},
{"request_id", next_request_id()}
};
return send_and_wait(msg);
}
// Subscribe to orderbook
bool subscribe(const std::string& symbol) {
json msg = {
{"type", "subscribe"},
{"symbols", json::array({symbol})},
{"request_id", next_request_id()}
};
return send(msg);
}
// Get orderbook (via subscribe)
std::optional<json> get_orderbook(const std::string& symbol) {
subscribe(symbol);
// Wait for orderbook message
return wait_response("", std::chrono::seconds(5));
}
// Ping
std::optional<json> ping() {
json msg = {
{"type", "ping"},
{"request_id", next_request_id()}
};
return send_and_wait(msg);
}
private:
void on_message(MessagePtr msg) {
try {
json j = json::parse(msg->get_payload());
if (config_.verbose) {
std::cout << "<< " << j.dump(2) << "\n";
}
std::lock_guard<std::mutex> lock(response_mutex_);
responses_.push(j);
response_cv_.notify_all();
} catch (const std::exception& e) {
std::cerr << "JSON parse error: " << e.what() << "\n";
}
}
bool send(const json& msg) {
if (!is_connected()) return false;
try {
std::string payload = msg.dump();
if (config_.verbose) {
std::cout << ">> " << msg.dump(2) << "\n";
}
ws_.send(connection_, payload, websocketpp::frame::opcode::text);
return true;
} catch (const std::exception& e) {
std::cerr << "Send error: " << e.what() << "\n";
return false;
}
}
std::optional<json> wait_response(const std::string& request_id,
std::chrono::seconds timeout = std::chrono::seconds(5)) {
std::unique_lock<std::mutex> lock(response_mutex_);
auto deadline = std::chrono::steady_clock::now() + timeout;
while (std::chrono::steady_clock::now() < deadline) {
if (!responses_.empty()) {
json resp = responses_.front();
responses_.pop();
// If request_id specified, match it
if (!request_id.empty()) {
if (resp.contains("request_id") && resp["request_id"] == request_id) {
return resp;
}
// Put it back if not matching (simple approach)
responses_.push(resp);
} else {
return resp;
}
}
response_cv_.wait_for(lock, std::chrono::milliseconds(100));
}
return std::nullopt;
}
std::optional<json> send_and_wait(const json& msg,
std::chrono::seconds timeout = std::chrono::seconds(5)) {
std::string req_id = msg["request_id"];
if (!send(msg)) {
return std::nullopt;
}
auto deadline = std::chrono::steady_clock::now() + timeout;
while (std::chrono::steady_clock::now() < deadline) {
std::unique_lock<std::mutex> lock(response_mutex_);
if (!responses_.empty()) {
json resp = responses_.front();
responses_.pop();
if (resp.contains("request_id") && resp["request_id"] == req_id) {
return resp;
}
// Handle non-matching responses (subscriptions, etc)
if (resp.contains("type")) {
std::string type = resp["type"];
if (type != "connected" && type != "pong") {
print_response(resp);
}
}
}
response_cv_.wait_for(lock, std::chrono::milliseconds(100));
}
return std::nullopt;
}
std::string next_request_id() {
return "req-" + std::to_string(++request_counter_);
}
void print_response(const json& resp) {
if (resp.contains("error") && !resp["error"].is_null()) {
std::cout << "Error: " << resp["error"] << "\n";
return;
}
if (!resp.contains("type")) {
std::cout << resp.dump(2) << "\n";
return;
}
std::string type = resp["type"];
if (type == "order_update" && resp.contains("data")) {
std::cout << "Order Update: " << resp["data"].dump() << "\n";
} else if (type == "position_update" && resp.contains("data")) {
std::cout << "Position Update: " << resp["data"].dump() << "\n";
} else if (type == "orderbook" && resp.contains("data")) {
auto& data = resp["data"];
if (data.contains("symbol")) {
std::cout << "OrderBook [" << data["symbol"] << "]:\n";
}
if (data.contains("bids") && data["bids"].is_array()) {
std::cout << " Bids: " << data["bids"].size() << " levels\n";
int count = 0;
for (const auto& bid : data["bids"]) {
if (count++ >= 5) break;
if (bid.contains("price") && bid.contains("size")) {
std::cout << " " << std::fixed << std::setprecision(2)
<< bid["price"].get<double>() << " @ "
<< std::setprecision(4) << bid["size"].get<double>() << "\n";
}
}
}
if (data.contains("asks") && data["asks"].is_array()) {
std::cout << " Asks: " << data["asks"].size() << " levels\n";
int count = 0;
for (const auto& ask : data["asks"]) {
if (count++ >= 5) break;
if (ask.contains("price") && ask.contains("size")) {
std::cout << " " << std::fixed << std::setprecision(2)
<< ask["price"].get<double>() << " @ "
<< std::setprecision(4) << ask["size"].get<double>() << "\n";
}
}
}
} else {
std::cout << resp.dump(2) << "\n";
}
}
Config config_;
WsClient ws_;
ConnectionHdl connection_;
std::thread io_thread_;
std::atomic<bool> connected_;
std::atomic<bool> authenticated_;
std::atomic<uint64_t> request_counter_;
std::atomic<bool> running_;
std::mutex connect_mutex_;
std::condition_variable connect_cv_;
std::mutex response_mutex_;
std::condition_variable response_cv_;
std::queue<json> responses_;
friend void print_message(Client& client, const json& msg);
};
//------------------------------------------------------------------------------
// CLI Interface
//------------------------------------------------------------------------------
void print_help() {
std::cout << R"(
LX CLI Commands:
place_order <symbol> <side> <type> <price> <size>
Example: place_order BTC-USD buy limit 50000 0.1
cancel_order <order_id>
Example: cancel_order 12345
get_orderbook <symbol>
Example: get_orderbook BTC-USD
get_positions
Show all open positions
get_orders
Show all open orders
get_balances
Show account balances
subscribe <symbol>
Subscribe to orderbook updates
ping
Test connection latency
help
Show this help message
quit / exit
Exit the CLI
)";
}
void print_message(const json& msg) {
if (msg.contains("error") && !msg["error"].is_null()) {
std::cout << "Error: " << msg["error"] << "\n";
return;
}
std::cout << msg.dump(2) << "\n";
}
std::vector<std::string> split(const std::string& s) {
std::vector<std::string> tokens;
std::istringstream iss(s);
std::string token;
while (iss >> token) {
tokens.push_back(token);
}
return tokens;
}
std::string trim(const std::string& s) {
size_t start = s.find_first_not_of(" \t\r\n");
if (start == std::string::npos) return "";
size_t end = s.find_last_not_of(" \t\r\n");
return s.substr(start, end - start + 1);
}
void run_interactive(Client& client) {
std::cout << "LX CLI - Type 'help' for commands\n> ";
std::string line;
while (std::getline(std::cin, line)) {
line = trim(line);
if (line.empty()) {
std::cout << "> ";
continue;
}
auto parts = split(line);
if (parts.empty()) {
std::cout << "> ";
continue;
}
std::string cmd = parts[0];
// Convert to lowercase
for (auto& c : cmd) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
if (cmd == "help") {
print_help();
} else if (cmd == "quit" || cmd == "exit") {
std::cout << "Goodbye\n";
break;
} else if (cmd == "place_order") {
if (parts.size() < 6) {
std::cout << "Usage: place_order <symbol> <side> <type> <price> <size>\n";
} else {
try {
double price = std::stod(parts[4]);
double size = std::stod(parts[5]);
auto resp = client.place_order(parts[1], parts[2], parts[3], price, size);
if (resp) {
print_message(*resp);
} else {
std::cout << "Timeout waiting for response\n";
}
} catch (const std::exception& e) {
std::cout << "Invalid price or size: " << e.what() << "\n";
}
}
} else if (cmd == "cancel_order") {
if (parts.size() < 2) {
std::cout << "Usage: cancel_order <order_id>\n";
} else {
try {
uint64_t order_id = std::stoull(parts[1]);
auto resp = client.cancel_order(order_id);
if (resp) {
print_message(*resp);
} else {
std::cout << "Timeout waiting for response\n";
}
} catch (const std::exception& e) {
std::cout << "Invalid order ID: " << e.what() << "\n";
}
}
} else if (cmd == "get_orderbook") {
if (parts.size() < 2) {
std::cout << "Usage: get_orderbook <symbol>\n";
} else {
auto resp = client.get_orderbook(parts[1]);
if (resp) {
print_message(*resp);
} else {
std::cout << "Subscribed to " << parts[1] << " orderbook\n";
}
}
} else if (cmd == "get_positions") {
auto resp = client.get_positions();
if (resp) {
print_message(*resp);
} else {
std::cout << "Timeout waiting for response\n";
}
} else if (cmd == "get_orders") {
auto resp = client.get_orders();
if (resp) {
print_message(*resp);
} else {
std::cout << "Timeout waiting for response\n";
}
} else if (cmd == "get_balances") {
auto resp = client.get_balances();
if (resp) {
print_message(*resp);
} else {
std::cout << "Timeout waiting for response\n";
}
} else if (cmd == "subscribe") {
if (parts.size() < 2) {
std::cout << "Usage: subscribe <symbol>\n";
} else {
if (client.subscribe(parts[1])) {
std::cout << "Subscribed to " << parts[1] << "\n";
} else {
std::cout << "Failed to subscribe\n";
}
}
} else if (cmd == "ping") {
auto start = std::chrono::high_resolution_clock::now();
auto resp = client.ping();
auto end = std::chrono::high_resolution_clock::now();
if (resp) {
auto latency = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "Pong: " << latency.count() << " us\n";
} else {
std::cout << "Ping timeout\n";
}
} else {
std::cout << "Unknown command: " << cmd << ". Type 'help' for commands.\n";
}
std::cout << "> ";
}
}
void run_command(Client& client, const std::vector<std::string>& args) {
if (args.empty()) {
std::cerr << "No command specified. Use -h for help.\n";
return;
}
std::string cmd = args[0];
// Convert to lowercase
for (auto& c : cmd) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
if (cmd == "place_order") {
if (args.size() < 6) {
std::cerr << "Usage: lx-cli place_order <symbol> <side> <type> <price> <size>\n";
std::exit(1);
}
try {
double price = std::stod(args[4]);
double size = std::stod(args[5]);
auto resp = client.place_order(args[1], args[2], args[3], price, size);
if (resp) {
std::cout << resp->dump(2) << "\n";
} else {
std::cerr << "Timeout\n";
std::exit(1);
}
} catch (const std::exception& e) {
std::cerr << "Invalid price or size: " << e.what() << "\n";
std::exit(1);
}
} else if (cmd == "cancel_order") {
if (args.size() < 2) {
std::cerr << "Usage: lx-cli cancel_order <order_id>\n";
std::exit(1);
}
try {
uint64_t order_id = std::stoull(args[1]);
auto resp = client.cancel_order(order_id);
if (resp) {
std::cout << resp->dump(2) << "\n";
} else {
std::cerr << "Timeout\n";
std::exit(1);
}
} catch (const std::exception& e) {
std::cerr << "Invalid order ID: " << e.what() << "\n";
std::exit(1);
}
} else if (cmd == "get_orderbook") {
if (args.size() < 2) {
std::cerr << "Usage: lx-cli get_orderbook <symbol>\n";
std::exit(1);
}
auto resp = client.get_orderbook(args[1]);
if (resp) {
std::cout << resp->dump(2) << "\n";
} else {
std::cerr << "Timeout\n";
std::exit(1);
}
} else if (cmd == "get_positions") {
auto resp = client.get_positions();
if (resp) {
std::cout << resp->dump(2) << "\n";
} else {
std::cerr << "Timeout\n";
std::exit(1);
}
} else if (cmd == "get_orders") {
auto resp = client.get_orders();
if (resp) {
std::cout << resp->dump(2) << "\n";
} else {
std::cerr << "Timeout\n";
std::exit(1);
}
} else if (cmd == "get_balances") {
auto resp = client.get_balances();
if (resp) {
std::cout << resp->dump(2) << "\n";
} else {
std::cerr << "Timeout\n";
std::exit(1);
}
} else if (cmd == "ping") {
auto start = std::chrono::high_resolution_clock::now();
auto resp = client.ping();
auto end = std::chrono::high_resolution_clock::now();
if (resp) {
auto latency = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "{\"latency_us\": " << latency.count() << "}\n";
} else {
std::cerr << "Timeout\n";
std::exit(1);
}
} else {
std::cerr << "Unknown command: " << cmd << "\n";
std::exit(1);
}
}
void print_usage(const char* prog) {
std::cout << "LX C++ CLI Trading Client\n\n"
<< "Usage: " << prog << " [options] [command] [args...]\n\n"
<< "Options:\n"
<< " -u, --url <url> WebSocket server URL (default: ws://localhost:8081)\n"
<< " -k, --key <key> API key for authentication\n"
<< " -s, --secret <secret> API secret for authentication\n"
<< " -i, --interactive Interactive mode (default if no command)\n"
<< " -v, --verbose Verbose output\n"
<< " -h, --help Show this help message\n\n"
<< "Commands:\n"
<< " place_order <symbol> <side> <type> <price> <size>\n"
<< " cancel_order <order_id>\n"
<< " get_orderbook <symbol>\n"
<< " get_positions\n"
<< " get_orders\n"
<< " get_balances\n"
<< " ping\n\n"
<< "Examples:\n"
<< " " << prog << " -i # Interactive mode\n"
<< " " << prog << " place_order BTC-USD buy limit 50000 0.1\n"
<< " " << prog << " cancel_order 12345\n"
<< " " << prog << " get_orderbook BTC-USD\n"
<< " " << prog << " -v ping # Ping with verbose output\n";
}
Config parse_args(int argc, char* argv[]) {
Config config;
int i = 1;
while (i < argc) {
std::string arg = argv[i];
if (arg == "-h" || arg == "--help") {
print_usage(argv[0]);
std::exit(0);
} else if (arg == "-u" || arg == "--url") {
if (i + 1 >= argc) {
std::cerr << "Missing URL argument\n";
std::exit(1);
}
config.ws_url = argv[++i];
} else if (arg == "-k" || arg == "--key") {
if (i + 1 >= argc) {
std::cerr << "Missing API key argument\n";
std::exit(1);
}
config.api_key = argv[++i];
} else if (arg == "-s" || arg == "--secret") {
if (i + 1 >= argc) {
std::cerr << "Missing API secret argument\n";
std::exit(1);
}
config.api_secret = argv[++i];
} else if (arg == "-i" || arg == "--interactive") {
config.interactive = true;
} else if (arg == "-v" || arg == "--verbose") {
config.verbose = true;
} else if (arg[0] != '-') {
// Command and its arguments
config.interactive = false;
while (i < argc) {
config.command_args.push_back(argv[i++]);
}
break;
} else {
std::cerr << "Unknown option: " << arg << "\n";
std::exit(1);
}
++i;
}
// Default to interactive if no command
if (config.command_args.empty()) {
config.interactive = true;
}
return config;
}
int main(int argc, char* argv[]) {
Config config = parse_args(argc, argv);
// Create and connect client
Client client(config);
if (!client.connect()) {
std::cerr << "Failed to connect to " << config.ws_url << "\n";
return 1;
}
// Authenticate if credentials provided
if (!config.api_key.empty() && !config.api_secret.empty()) {
if (!client.authenticate()) {
std::cerr << "Authentication failed\n";
return 1;
}
if (config.verbose) {
std::cout << "Authenticated\n";
}
}
// Run in interactive or command mode
if (config.interactive) {
run_interactive(client);
} else {
run_command(client, config.command_args);
}
return 0;
}
-109
View File
@@ -1,109 +0,0 @@
cmake_minimum_required(VERSION 3.16)
# Allow older cmake_minimum_required in fetched projects
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum policy version for compatibility")
project(lxdex VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Options
option(LXDEX_BUILD_EXAMPLES "Build example applications" ON)
option(LXDEX_BUILD_TESTS "Build tests" OFF)
# Find dependencies
find_package(Threads REQUIRED)
# Include FetchContent for external dependencies
include(FetchContent)
# nlohmann/json
FetchContent_Declare(
json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(json)
# websocketpp - header-only, just need source dir
FetchContent_Declare(
websocketpp
GIT_REPOSITORY https://github.com/zaphoyd/websocketpp.git
GIT_TAG 0.8.2
)
FetchContent_Populate(websocketpp)
# asio (standalone, no Boost)
FetchContent_Declare(
asio
GIT_REPOSITORY https://github.com/chriskohlhoff/asio.git
GIT_TAG asio-1-28-2
)
FetchContent_Populate(asio)
# Library target
add_library(lxdex
src/client.cpp
src/orderbook.cpp
)
target_include_directories(lxdex
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${websocketpp_SOURCE_DIR}
${asio_SOURCE_DIR}/asio/include
)
target_link_libraries(lxdex
PUBLIC
nlohmann_json::nlohmann_json
PRIVATE
Threads::Threads
)
target_compile_definitions(lxdex
PRIVATE
ASIO_STANDALONE
_WEBSOCKETPP_CPP11_STL_
)
# Platform-specific settings
if(APPLE)
target_compile_definitions(lxdex PRIVATE _DARWIN_C_SOURCE)
endif()
if(WIN32)
target_link_libraries(lxdex PRIVATE ws2_32)
endif()
# Examples
if(LXDEX_BUILD_EXAMPLES)
add_executable(lxdex_basic examples/basic.cpp)
target_link_libraries(lxdex_basic PRIVATE lxdex)
target_include_directories(lxdex_basic
PRIVATE
${websocketpp_SOURCE_DIR}
${asio_SOURCE_DIR}/asio/include
)
target_compile_definitions(lxdex_basic
PRIVATE
ASIO_STANDALONE
_WEBSOCKETPP_CPP11_STL_
)
endif()
# Install
install(TARGETS lxdex
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
)
install(DIRECTORY include/
DESTINATION include
)
-364
View File
@@ -1,364 +0,0 @@
# LX C++ SDK
Production-ready C++17 SDK for the LX Decentralized Exchange.
## Features
- WebSocket client with automatic reconnection
- Full trading API (orders, positions, balances)
- Real-time market data subscriptions
- Thread-safe local orderbook management
- RAII-compliant resource management
- Zero external runtime dependencies (header-only deps fetched at build)
## Requirements
- C++17 compiler (GCC 8+, Clang 7+, MSVC 2019+)
- CMake 3.16+
- Git (for dependency fetching)
## Building
```bash
mkdir build && cd build
cmake ..
cmake --build .
```
### Build Options
```bash
cmake -DLXDEX_BUILD_EXAMPLES=ON .. # Build examples (default: ON)
cmake -DLXDEX_BUILD_TESTS=ON .. # Build tests (default: OFF)
```
## Quick Start
```cpp
#include <lxdex/client.hpp>
#include <iostream>
int main() {
// Configure
lxdex::ClientConfig config;
config.ws_url = "ws://localhost:8081";
config.api_key = "your_key";
config.api_secret = "your_secret";
// Create client
auto client = lxdex::make_client(config);
// Connect
if (auto err = client->connect(); err) {
std::cerr << "Connect failed: " << err.message << "\n";
return 1;
}
// Authenticate
if (auto err = client->authenticate(); err) {
std::cerr << "Auth failed: " << err.message << "\n";
return 1;
}
// Place order
lxdex::Order order;
order.symbol = "BTC-USDT";
order.side = lxdex::Side::Buy;
order.type = lxdex::OrderType::Limit;
order.price = 40000.0;
order.size = 0.01;
auto result = client->place_order(order);
if (result.ok()) {
std::cout << "Order ID: " << result.value.order_id << "\n";
}
// Subscribe to orderbook
client->subscribe_orderbook({"BTC-USDT"}, [](const lxdex::OrderBook& ob) {
std::cout << "Best bid: " << ob.best_bid()
<< " Best ask: " << ob.best_ask() << "\n";
});
// Keep running...
return 0;
}
```
## API Reference
### Client Configuration
```cpp
struct ClientConfig {
std::string ws_url = "ws://localhost:8081";
std::string api_key;
std::string api_secret;
std::chrono::seconds connect_timeout{10};
std::chrono::seconds ping_interval{30};
std::chrono::seconds reconnect_delay{5};
int max_reconnect_attempts = 5;
bool auto_reconnect = true;
};
```
### Connection
```cpp
// Connect to server
Error connect();
// Disconnect
void disconnect();
// Check connection state
bool is_connected() const;
ConnectionState state() const;
// Authenticate
Error authenticate();
bool is_authenticated() const;
```
### Trading
```cpp
// Place order
Result<OrderResponse> place_order(const Order& order);
std::future<Result<OrderResponse>> place_order_async(const Order& order);
// Cancel order
Error cancel_order(uint64_t order_id);
std::future<Error> cancel_order_async(uint64_t order_id);
// Modify order
Error modify_order(uint64_t order_id, double new_price, double new_size);
// Cancel all
Result<int> cancel_all_orders(const std::string& symbol = "");
```
### Market Data
```cpp
// Get orderbook snapshot
Result<OrderBook> get_orderbook(const std::string& symbol, int32_t depth = 20);
// Get recent trades
Result<std::vector<Trade>> get_trades(const std::string& symbol, int32_t limit = 100);
// Subscribe to updates
Error subscribe_orderbook(const std::vector<std::string>& symbols, OrderBookCallback cb);
Error subscribe_trades(const std::vector<std::string>& symbols, TradeCallback cb);
// Unsubscribe
Error unsubscribe(const std::string& channel, const std::vector<std::string>& symbols);
```
### Account
```cpp
Result<std::vector<Balance>> get_balances();
Result<std::vector<Position>> get_positions();
Result<std::vector<Order>> get_orders();
Result<NodeInfo> get_info();
```
### Callbacks
```cpp
void on_error(ErrorCallback callback);
void on_order(OrderCallback callback);
void on_trade(TradeCallback callback);
void on_connection(ConnectionCallback callback);
void on_message(MessageCallback callback); // Raw messages
```
### Local Data
```cpp
// Access local caches
OrderBookManager& orderbooks();
OrderTracker& orders();
TradeTracker& trades();
```
### Metrics
```cpp
ClientMetrics metrics() const;
void reset_metrics();
Result<int64_t> ping(); // Returns latency in microseconds
```
## Types
### Order
```cpp
struct Order {
uint64_t order_id;
std::string symbol;
OrderType type; // Limit, Market, Stop, StopLimit, Iceberg, Peg
Side side; // Buy, Sell
double price;
double size;
double filled;
double remaining;
OrderStatus status; // Open, Partial, Filled, Cancelled, Rejected
std::string user_id;
std::string client_id;
int64_t timestamp;
TimeInForce time_in_force; // GTC, IOC, FOK, DAY
bool post_only;
bool reduce_only;
bool is_open() const;
bool is_closed() const;
double fill_rate() const;
};
```
### Trade
```cpp
struct Trade {
uint64_t trade_id;
std::string symbol;
double price;
double size;
Side side;
uint64_t buy_order_id;
uint64_t sell_order_id;
std::string buyer_id;
std::string seller_id;
int64_t timestamp;
double total_value() const;
};
```
### OrderBook
```cpp
struct OrderBook {
std::string symbol;
std::vector<PriceLevel> bids;
std::vector<PriceLevel> asks;
int64_t timestamp;
double best_bid() const;
double best_ask() const;
double spread() const;
double mid_price() const;
double spread_percentage() const;
};
```
### Error Handling
```cpp
struct Error {
int code;
std::string message;
std::string request_id;
explicit operator bool() const; // true if error
};
template<typename T>
struct Result {
T value;
Error error;
bool ok() const;
explicit operator bool() const; // true if ok
};
```
## Thread Safety
- All client methods are thread-safe
- Callbacks are invoked from the IO thread; avoid blocking
- Local orderbook/trade managers use internal locking
## Example: High-Frequency Trading
```cpp
#include <lxdex/client.hpp>
int main() {
lxdex::ClientConfig config;
config.ws_url = "ws://localhost:8081";
config.api_key = "key";
config.api_secret = "secret";
auto client = lxdex::make_client(config);
client->connect();
client->authenticate();
// Track local orderbook
client->subscribe_orderbook({"BTC-USDT"}, [&](const lxdex::OrderBook& ob) {
double mid = ob.mid_price();
double spread = ob.spread();
// Simple market making logic
if (spread > 10.0) {
// Place bid slightly below mid
lxdex::Order bid;
bid.symbol = "BTC-USDT";
bid.side = lxdex::Side::Buy;
bid.type = lxdex::OrderType::Limit;
bid.price = mid - 2.0;
bid.size = 0.001;
bid.post_only = true;
bid.client_id = lxdex::Client::generate_client_id();
client->place_order_async(bid);
// Place ask slightly above mid
lxdex::Order ask = bid;
ask.side = lxdex::Side::Sell;
ask.price = mid + 2.0;
ask.client_id = lxdex::Client::generate_client_id();
client->place_order_async(ask);
}
});
// Handle fills
client->on_order([](const lxdex::Order& order) {
if (order.status == lxdex::OrderStatus::Filled) {
std::cout << "Filled: " << order.order_id << "\n";
}
});
// Run until interrupted
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
```
## Integration with CMake Projects
```cmake
find_package(lxdex REQUIRED)
target_link_libraries(your_target PRIVATE lxdex::lxdex)
```
Or use FetchContent:
```cmake
FetchContent_Declare(
lxdex
GIT_REPOSITORY https://github.com/luxfi/dex.git
SOURCE_SUBDIR sdk/cpp
)
FetchContent_MakeAvailable(lxdex)
target_link_libraries(your_target PRIVATE lxdex)
```
## License
MIT License - Copyright (c) 2025 Lux Partners Limited
-250
View File
@@ -1,250 +0,0 @@
// LX C++ SDK - Basic Usage Example
// Copyright (c) 2025 Lux Partners Limited
// SPDX-License-Identifier: MIT
#include <lxdex/client.hpp>
#include <iostream>
#include <iomanip>
#include <thread>
#include <csignal>
#include <atomic>
std::atomic<bool> g_running{true};
void signal_handler(int signal) {
std::cout << "\nReceived signal " << signal << ", shutting down...\n";
g_running = false;
}
void print_orderbook(const lxdex::OrderBook& ob) {
std::cout << "\n=== " << ob.symbol << " Orderbook ===\n";
std::cout << std::fixed << std::setprecision(2);
std::cout << "Asks:\n";
size_t ask_count = std::min(ob.asks.size(), size_t{5});
for (size_t i = ask_count; i > 0; --i) {
const auto& level = ob.asks[i - 1];
std::cout << " " << std::setw(12) << level.price
<< " | " << std::setw(10) << level.size << "\n";
}
std::cout << "----------------------------\n";
std::cout << "Bids:\n";
for (size_t i = 0; i < std::min(ob.bids.size(), size_t{5}); ++i) {
const auto& level = ob.bids[i];
std::cout << " " << std::setw(12) << level.price
<< " | " << std::setw(10) << level.size << "\n";
}
std::cout << "\nSpread: " << ob.spread() << " ("
<< std::setprecision(4) << ob.spread_percentage() << "%)\n";
std::cout << "Mid: " << std::setprecision(2) << ob.mid_price() << "\n";
}
void print_trade(const lxdex::Trade& trade) {
std::cout << "[Trade] " << trade.symbol
<< " " << (trade.side == lxdex::Side::Buy ? "BUY" : "SELL")
<< " " << trade.size << " @ " << trade.price
<< " (id: " << trade.trade_id << ")\n";
}
void print_order(const lxdex::Order& order) {
std::cout << "[Order] " << order.symbol
<< " " << (order.side == lxdex::Side::Buy ? "BUY" : "SELL")
<< " " << order.size << " @ " << order.price
<< " status: ";
switch (order.status) {
case lxdex::OrderStatus::Open: std::cout << "OPEN"; break;
case lxdex::OrderStatus::Partial: std::cout << "PARTIAL"; break;
case lxdex::OrderStatus::Filled: std::cout << "FILLED"; break;
case lxdex::OrderStatus::Cancelled: std::cout << "CANCELLED"; break;
case lxdex::OrderStatus::Rejected: std::cout << "REJECTED"; break;
}
std::cout << " (id: " << order.order_id << ")\n";
}
int main(int argc, char* argv[]) {
// Setup signal handler
std::signal(SIGINT, signal_handler);
std::signal(SIGTERM, signal_handler);
std::cout << "LX C++ SDK Example\n";
std::cout << "======================\n\n";
// Configure client
lxdex::ClientConfig config;
config.ws_url = "ws://localhost:8081";
config.auto_reconnect = true;
config.max_reconnect_attempts = 5;
// Parse command line args
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--url" && i + 1 < argc) {
config.ws_url = argv[++i];
} else if (arg == "--key" && i + 1 < argc) {
config.api_key = argv[++i];
} else if (arg == "--secret" && i + 1 < argc) {
config.api_secret = argv[++i];
} else if (arg == "--help") {
std::cout << "Usage: " << argv[0] << " [options]\n"
<< " --url URL WebSocket URL (default: ws://localhost:8081)\n"
<< " --key KEY API key for authentication\n"
<< " --secret SEC API secret for authentication\n"
<< " --help Show this help\n";
return 0;
}
}
// Create client
auto client = lxdex::make_client(config);
// Setup callbacks
client->on_error([](const lxdex::Error& err) {
std::cerr << "[Error] " << err.message;
if (!err.request_id.empty()) {
std::cerr << " (request: " << err.request_id << ")";
}
std::cerr << "\n";
});
client->on_connection([](lxdex::ConnectionState state) {
std::cout << "[Connection] State: ";
switch (state) {
case lxdex::ConnectionState::Disconnected:
std::cout << "DISCONNECTED";
break;
case lxdex::ConnectionState::Connecting:
std::cout << "CONNECTING";
break;
case lxdex::ConnectionState::Connected:
std::cout << "CONNECTED";
break;
case lxdex::ConnectionState::Reconnecting:
std::cout << "RECONNECTING";
break;
case lxdex::ConnectionState::Failed:
std::cout << "FAILED";
break;
}
std::cout << "\n";
});
client->on_order([](const lxdex::Order& order) {
print_order(order);
});
// Connect
std::cout << "Connecting to " << config.ws_url << "...\n";
auto err = client->connect();
if (err) {
std::cerr << "Failed to connect: " << err.message << "\n";
return 1;
}
std::cout << "Connected!\n\n";
// Ping test
auto ping_result = client->ping();
if (ping_result.ok()) {
std::cout << "Ping: " << ping_result.value << " us\n\n";
}
// Authenticate if credentials provided
if (!config.api_key.empty()) {
std::cout << "Authenticating...\n";
err = client->authenticate();
if (err) {
std::cerr << "Authentication failed: " << err.message << "\n";
} else {
std::cout << "Authenticated!\n\n";
}
}
// Subscribe to orderbook updates
std::vector<std::string> symbols = {"BTC-USDT", "ETH-USDT"};
std::cout << "Subscribing to orderbook updates...\n";
err = client->subscribe_orderbook(symbols, [](const lxdex::OrderBook& ob) {
print_orderbook(ob);
});
if (err) {
std::cerr << "Failed to subscribe: " << err.message << "\n";
}
// Subscribe to trades
std::cout << "Subscribing to trade updates...\n";
err = client->subscribe_trades(symbols, [](const lxdex::Trade& trade) {
print_trade(trade);
});
if (err) {
std::cerr << "Failed to subscribe: " << err.message << "\n";
}
// Example: Place a test order (if authenticated)
if (client->is_authenticated()) {
std::cout << "\nPlacing test order...\n";
lxdex::Order order;
order.symbol = "BTC-USDT";
order.side = lxdex::Side::Buy;
order.type = lxdex::OrderType::Limit;
order.price = 40000.0;
order.size = 0.001;
order.time_in_force = lxdex::TimeInForce::GTC;
order.client_id = lxdex::Client::generate_client_id();
auto result = client->place_order(order);
if (result.ok()) {
std::cout << "Order placed! ID: " << result.value.order_id
<< " Status: " << result.value.status << "\n";
// Cancel the order after a brief delay
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Cancelling order...\n";
err = client->cancel_order(result.value.order_id);
if (err) {
std::cerr << "Failed to cancel: " << err.message << "\n";
} else {
std::cout << "Order cancelled!\n";
}
} else {
std::cerr << "Failed to place order: " << result.error.message << "\n";
}
}
// Main loop - wait for data
std::cout << "\nWaiting for market data (Ctrl+C to exit)...\n\n";
int update_count = 0;
while (g_running) {
std::this_thread::sleep_for(std::chrono::seconds(1));
// Periodic metrics report
if (++update_count % 30 == 0) {
auto metrics = client->metrics();
std::cout << "\n--- Metrics ---\n"
<< "Messages sent: " << metrics.messages_sent << "\n"
<< "Messages received: " << metrics.messages_received << "\n"
<< "Orders sent: " << metrics.orders_sent << "\n"
<< "Trades received: " << metrics.trades_received << "\n"
<< "Last latency: " << metrics.last_latency_us << " us\n"
<< "Reconnects: " << metrics.reconnect_count << "\n"
<< "Errors: " << metrics.error_count << "\n\n";
}
// Check connection
if (!client->is_connected()) {
std::cout << "Connection lost, waiting for reconnect...\n";
}
}
// Cleanup
std::cout << "\nDisconnecting...\n";
client->disconnect();
std::cout << "Done.\n";
return 0;
}
-286
View File
@@ -1,286 +0,0 @@
// LX C++ SDK - Client
// Copyright (c) 2025 Lux Partners Limited
// SPDX-License-Identifier: MIT
#ifndef LXDEX_CLIENT_HPP
#define LXDEX_CLIENT_HPP
#include "types.hpp"
#include "orderbook.hpp"
#include <functional>
#include <memory>
#include <future>
#include <atomic>
#include <chrono>
namespace lxdex {
/// Connection state
enum class ConnectionState {
Disconnected,
Connecting,
Connected,
Reconnecting,
Failed
};
/// Client configuration
struct ClientConfig {
std::string ws_url = "ws://localhost:8081";
std::string api_key;
std::string api_secret;
// Connection settings
std::chrono::seconds connect_timeout{10};
std::chrono::seconds ping_interval{30};
std::chrono::seconds reconnect_delay{5};
int max_reconnect_attempts = 5;
bool auto_reconnect = true;
// Performance settings
size_t send_queue_size = 10000;
size_t recv_queue_size = 10000;
};
/// Client metrics
struct ClientMetrics {
uint64_t messages_sent = 0;
uint64_t messages_received = 0;
uint64_t orders_sent = 0;
uint64_t trades_received = 0;
int64_t last_latency_us = 0;
int64_t avg_latency_us = 0;
uint64_t reconnect_count = 0;
uint64_t error_count = 0;
};
/// Error information
struct Error {
int code = 0;
std::string message;
std::string request_id;
explicit operator bool() const noexcept { return code != 0; }
};
/// Result type for async operations
template<typename T>
struct Result {
T value;
Error error;
[[nodiscard]] bool ok() const noexcept { return !error; }
explicit operator bool() const noexcept { return ok(); }
};
/// Callback types
using ErrorCallback = std::function<void(const Error&)>;
using OrderCallback = std::function<void(const Order&)>;
using TradeCallback = std::function<void(const Trade&)>;
using OrderBookCallback = std::function<void(const OrderBook&)>;
using MessageCallback = std::function<void(const Message&)>;
using ConnectionCallback = std::function<void(ConnectionState)>;
/// LX WebSocket Client
/// Thread-safe, RAII-compliant client for trading operations
class Client {
public:
/// Construct client with configuration
explicit Client(ClientConfig config = {});
/// Destructor - ensures clean disconnection
~Client();
// Non-copyable, non-movable (owns thread resources)
Client(const Client&) = delete;
Client& operator=(const Client&) = delete;
Client(Client&&) = delete;
Client& operator=(Client&&) = delete;
//--------------------------------------------------------------------------
// Connection
//--------------------------------------------------------------------------
/// Connect to server
/// @return Error if connection fails
Error connect();
/// Disconnect from server
void disconnect();
/// Check if connected
[[nodiscard]] bool is_connected() const noexcept;
/// Get connection state
[[nodiscard]] ConnectionState state() const noexcept;
/// Authenticate with API credentials
/// @return Error if authentication fails
Error authenticate();
/// Check if authenticated
[[nodiscard]] bool is_authenticated() const noexcept;
//--------------------------------------------------------------------------
// Trading Operations
//--------------------------------------------------------------------------
/// Place a new order
/// @param order Order to place
/// @return Result with order response
Result<OrderResponse> place_order(const Order& order);
/// Place order asynchronously
/// @param order Order to place
/// @return Future with result
std::future<Result<OrderResponse>> place_order_async(const Order& order);
/// Cancel an order
/// @param order_id Order ID to cancel
/// @return Error if cancellation fails
Error cancel_order(uint64_t order_id);
/// Cancel order asynchronously
std::future<Error> cancel_order_async(uint64_t order_id);
/// Modify an existing order
/// @param order_id Order ID to modify
/// @param new_price New price (0 to keep current)
/// @param new_size New size (0 to keep current)
/// @return Error if modification fails
Error modify_order(uint64_t order_id, double new_price, double new_size);
/// Cancel all orders for a symbol
/// @param symbol Symbol to cancel orders for (empty for all)
/// @return Number of orders cancelled
Result<int> cancel_all_orders(const std::string& symbol = "");
//--------------------------------------------------------------------------
// Market Data
//--------------------------------------------------------------------------
/// Get current orderbook snapshot
/// @param symbol Trading pair symbol
/// @param depth Number of levels (0 for all)
/// @return Result with orderbook
Result<OrderBook> get_orderbook(const std::string& symbol, int32_t depth = 20);
/// Get recent trades
/// @param symbol Trading pair symbol
/// @param limit Maximum number of trades
/// @return Result with trades
Result<std::vector<Trade>> get_trades(const std::string& symbol, int32_t limit = 100);
/// Subscribe to orderbook updates
/// @param symbols Symbols to subscribe to
/// @param callback Callback for updates
/// @return Error if subscription fails
Error subscribe_orderbook(
const std::vector<std::string>& symbols,
OrderBookCallback callback
);
/// Subscribe to trade updates
/// @param symbols Symbols to subscribe to
/// @param callback Callback for trades
/// @return Error if subscription fails
Error subscribe_trades(
const std::vector<std::string>& symbols,
TradeCallback callback
);
/// Unsubscribe from channel
/// @param channel Channel name (e.g., "orderbook", "trades")
/// @param symbols Symbols to unsubscribe from
/// @return Error if unsubscription fails
Error unsubscribe(
const std::string& channel,
const std::vector<std::string>& symbols
);
//--------------------------------------------------------------------------
// Account
//--------------------------------------------------------------------------
/// Get account balances
/// @return Result with balances
Result<std::vector<Balance>> get_balances();
/// Get open positions
/// @return Result with positions
Result<std::vector<Position>> get_positions();
/// Get open orders
/// @return Result with orders
Result<std::vector<Order>> get_orders();
/// Get node information
/// @return Result with node info
Result<NodeInfo> get_info();
//--------------------------------------------------------------------------
// Callbacks
//--------------------------------------------------------------------------
/// Set error callback
void on_error(ErrorCallback callback);
/// Set order update callback
void on_order(OrderCallback callback);
/// Set trade callback
void on_trade(TradeCallback callback);
/// Set connection state callback
void on_connection(ConnectionCallback callback);
/// Set raw message callback (for debugging)
void on_message(MessageCallback callback);
//--------------------------------------------------------------------------
// Local Data
//--------------------------------------------------------------------------
/// Get local orderbook manager
[[nodiscard]] OrderBookManager& orderbooks() noexcept;
/// Get order tracker
[[nodiscard]] OrderTracker& orders() noexcept;
/// Get trade tracker
[[nodiscard]] TradeTracker& trades() noexcept;
//--------------------------------------------------------------------------
// Metrics
//--------------------------------------------------------------------------
/// Get client metrics
[[nodiscard]] ClientMetrics metrics() const noexcept;
/// Reset metrics
void reset_metrics();
//--------------------------------------------------------------------------
// Utility
//--------------------------------------------------------------------------
/// Send ping and measure latency
/// @return Round-trip time in microseconds
Result<int64_t> ping();
/// Generate client order ID
[[nodiscard]] static std::string generate_client_id();
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
/// Convenience factory function
inline std::unique_ptr<Client> make_client(ClientConfig config = {}) {
return std::make_unique<Client>(std::move(config));
}
} // namespace lxdex
#endif // LXDEX_CLIENT_HPP
-188
View File
@@ -1,188 +0,0 @@
// LX C++ SDK - Orderbook Interface
// Copyright (c) 2025 Lux Partners Limited
// SPDX-License-Identifier: MIT
#ifndef LXDEX_ORDERBOOK_HPP
#define LXDEX_ORDERBOOK_HPP
#include "types.hpp"
#include <map>
#include <unordered_map>
#include <mutex>
#include <functional>
#include <atomic>
namespace lxdex {
/// Local orderbook for market data tracking
/// Thread-safe implementation for real-time updates
class LocalOrderBook {
public:
explicit LocalOrderBook(std::string symbol);
~LocalOrderBook() = default;
// Non-copyable, non-movable (contains atomic and mutex)
LocalOrderBook(const LocalOrderBook&) = delete;
LocalOrderBook& operator=(const LocalOrderBook&) = delete;
LocalOrderBook(LocalOrderBook&&) = delete;
LocalOrderBook& operator=(LocalOrderBook&&) = delete;
/// Get symbol
[[nodiscard]] const std::string& symbol() const noexcept { return symbol_; }
/// Apply full snapshot
void apply_snapshot(const OrderBook& snapshot);
/// Update single price level
void update_level(Side side, double price, double size);
/// Remove price level
void remove_level(Side side, double price);
/// Get current snapshot
[[nodiscard]] OrderBook get_snapshot(int32_t depth = 0) const;
/// Get best bid
[[nodiscard]] std::optional<PriceLevel> best_bid() const;
/// Get best ask
[[nodiscard]] std::optional<PriceLevel> best_ask() const;
/// Get mid price
[[nodiscard]] double mid_price() const;
/// Get spread
[[nodiscard]] double spread() const;
/// Get total bid depth
[[nodiscard]] double bid_depth() const;
/// Get total ask depth
[[nodiscard]] double ask_depth() const;
/// Clear all levels
void clear();
/// Get last update timestamp
[[nodiscard]] int64_t last_update() const noexcept { return last_update_.load(); }
/// Set callback for orderbook updates
void on_update(std::function<void(const OrderBook&)> callback);
private:
std::string symbol_;
// Bids: highest price first (descending)
std::map<double, PriceLevel, std::greater<double>> bids_;
// Asks: lowest price first (ascending)
std::map<double, PriceLevel, std::less<double>> asks_;
std::atomic<int64_t> last_update_{0};
mutable std::mutex mutex_;
std::function<void(const OrderBook&)> update_callback_;
void notify_update();
};
/// Order tracker for managing local order state
class OrderTracker {
public:
OrderTracker() = default;
~OrderTracker() = default;
/// Add or update order
void upsert(const Order& order);
/// Remove order by ID
bool remove(uint64_t order_id);
/// Get order by ID
[[nodiscard]] std::optional<Order> get(uint64_t order_id) const;
/// Get all orders for symbol
[[nodiscard]] std::vector<Order> get_by_symbol(const std::string& symbol) const;
/// Get all open orders
[[nodiscard]] std::vector<Order> get_open() const;
/// Get all orders
[[nodiscard]] std::vector<Order> get_all() const;
/// Clear all orders
void clear();
/// Get order count
[[nodiscard]] size_t count() const;
private:
std::unordered_map<uint64_t, Order> orders_;
mutable std::mutex mutex_;
};
/// Trade tracker for recent trades
class TradeTracker {
public:
explicit TradeTracker(size_t max_trades = 1000);
~TradeTracker() = default;
/// Add trade
void add(const Trade& trade);
/// Get recent trades for symbol
[[nodiscard]] std::vector<Trade> get_by_symbol(
const std::string& symbol,
size_t limit = 100
) const;
/// Get all recent trades
[[nodiscard]] std::vector<Trade> get_recent(size_t limit = 100) const;
/// Clear all trades
void clear();
/// Get trade count
[[nodiscard]] size_t count() const;
/// Set callback for new trades
void on_trade(std::function<void(const Trade&)> callback);
private:
std::vector<Trade> trades_;
size_t max_trades_;
mutable std::mutex mutex_;
std::function<void(const Trade&)> trade_callback_;
};
/// Multi-symbol orderbook manager
class OrderBookManager {
public:
OrderBookManager() = default;
~OrderBookManager() = default;
/// Get or create orderbook for symbol
LocalOrderBook& get_or_create(const std::string& symbol);
/// Get orderbook for symbol (returns nullptr if not exists)
LocalOrderBook* get(const std::string& symbol);
/// Check if orderbook exists
[[nodiscard]] bool has(const std::string& symbol) const;
/// Remove orderbook
bool remove(const std::string& symbol);
/// Get all symbols
[[nodiscard]] std::vector<std::string> symbols() const;
/// Clear all orderbooks
void clear();
private:
std::unordered_map<std::string, std::unique_ptr<LocalOrderBook>> books_;
mutable std::mutex mutex_;
};
} // namespace lxdex
#endif // LXDEX_ORDERBOOK_HPP
-376
View File
@@ -1,376 +0,0 @@
// LX C++ SDK - Type Definitions
// Copyright (c) 2025 Lux Partners Limited
// SPDX-License-Identifier: MIT
#ifndef LXDEX_TYPES_HPP
#define LXDEX_TYPES_HPP
#include <cstdint>
#include <string>
#include <vector>
#include <chrono>
#include <optional>
#include <nlohmann/json.hpp>
namespace lxdex {
/// Order side
enum class Side : int32_t {
Buy = 0,
Sell = 1
};
/// Order type
enum class OrderType : int32_t {
Limit = 0,
Market = 1,
Stop = 2,
StopLimit = 3,
Iceberg = 4,
Peg = 5
};
/// Order status
enum class OrderStatus {
Open,
Partial,
Filled,
Cancelled,
Rejected
};
/// Time in force
enum class TimeInForce {
GTC, // Good Till Cancelled
IOC, // Immediate Or Cancel
FOK, // Fill Or Kill
DAY // Day Order
};
/// Trading order
struct Order {
uint64_t order_id = 0;
std::string symbol;
OrderType type = OrderType::Limit;
Side side = Side::Buy;
double price = 0.0;
double size = 0.0;
double filled = 0.0;
double remaining = 0.0;
OrderStatus status = OrderStatus::Open;
std::string user_id;
std::string client_id;
int64_t timestamp = 0;
TimeInForce time_in_force = TimeInForce::GTC;
bool post_only = false;
bool reduce_only = false;
/// Check if order is open
[[nodiscard]] bool is_open() const noexcept {
return status == OrderStatus::Open || status == OrderStatus::Partial;
}
/// Check if order is closed
[[nodiscard]] bool is_closed() const noexcept {
return status == OrderStatus::Filled ||
status == OrderStatus::Cancelled ||
status == OrderStatus::Rejected;
}
/// Get fill rate [0.0, 1.0]
[[nodiscard]] double fill_rate() const noexcept {
return (size > 0.0) ? filled / size : 0.0;
}
};
/// Order response from server
struct OrderResponse {
uint64_t order_id = 0;
std::string status;
std::string message;
};
/// Executed trade
struct Trade {
uint64_t trade_id = 0;
std::string symbol;
double price = 0.0;
double size = 0.0;
Side side = Side::Buy;
uint64_t buy_order_id = 0;
uint64_t sell_order_id = 0;
std::string buyer_id;
std::string seller_id;
int64_t timestamp = 0;
/// Get total trade value
[[nodiscard]] double total_value() const noexcept {
return price * size;
}
/// Get timestamp as time_point
[[nodiscard]] std::chrono::system_clock::time_point timestamp_time() const noexcept {
return std::chrono::system_clock::from_time_t(timestamp);
}
};
/// Price level in orderbook
struct PriceLevel {
double price = 0.0;
double size = 0.0;
int32_t count = 0;
};
/// Orderbook snapshot
struct OrderBook {
std::string symbol;
std::vector<PriceLevel> bids;
std::vector<PriceLevel> asks;
int64_t timestamp = 0;
/// Get best bid price (0 if empty)
[[nodiscard]] double best_bid() const noexcept {
return bids.empty() ? 0.0 : bids.front().price;
}
/// Get best ask price (0 if empty)
[[nodiscard]] double best_ask() const noexcept {
return asks.empty() ? 0.0 : asks.front().price;
}
/// Get bid-ask spread
[[nodiscard]] double spread() const noexcept {
double bid = best_bid();
double ask = best_ask();
return (bid > 0.0 && ask > 0.0) ? (ask - bid) : 0.0;
}
/// Get mid price
[[nodiscard]] double mid_price() const noexcept {
double bid = best_bid();
double ask = best_ask();
return (bid > 0.0 && ask > 0.0) ? (bid + ask) / 2.0 : 0.0;
}
/// Get spread as percentage of mid price
[[nodiscard]] double spread_percentage() const noexcept {
double mid = mid_price();
return (mid > 0.0) ? (spread() / mid) * 100.0 : 0.0;
}
};
/// Node/server information
struct NodeInfo {
std::string version;
std::string network;
int64_t order_count = 0;
int64_t trade_count = 0;
int64_t timestamp = 0;
int64_t block_height = 0;
bool syncing = false;
int64_t uptime = 0;
};
/// Account balance
struct Balance {
std::string asset;
double available = 0.0;
double locked = 0.0;
double total = 0.0;
/// Get utilization ratio [0.0, 1.0]
[[nodiscard]] double utilization() const noexcept {
return (total > 0.0) ? locked / total : 0.0;
}
};
/// Trading position
struct Position {
std::string symbol;
double size = 0.0;
double entry_price = 0.0;
double mark_price = 0.0;
double pnl = 0.0;
double margin = 0.0;
/// Calculate unrealized PnL
[[nodiscard]] double unrealized_pnl() const noexcept {
return (mark_price - entry_price) * size;
}
/// Calculate PnL percentage
[[nodiscard]] double pnl_percentage() const noexcept {
return (entry_price > 0.0)
? ((mark_price - entry_price) / entry_price) * 100.0
: 0.0;
}
};
/// WebSocket message
struct Message {
std::string type;
nlohmann::json data;
std::string error;
std::string request_id;
int64_t timestamp = 0;
};
// JSON serialization
NLOHMANN_JSON_SERIALIZE_ENUM(Side, {
{Side::Buy, "buy"},
{Side::Sell, "sell"}
})
NLOHMANN_JSON_SERIALIZE_ENUM(OrderType, {
{OrderType::Limit, "limit"},
{OrderType::Market, "market"},
{OrderType::Stop, "stop"},
{OrderType::StopLimit, "stop_limit"},
{OrderType::Iceberg, "iceberg"},
{OrderType::Peg, "peg"}
})
NLOHMANN_JSON_SERIALIZE_ENUM(OrderStatus, {
{OrderStatus::Open, "open"},
{OrderStatus::Partial, "partial"},
{OrderStatus::Filled, "filled"},
{OrderStatus::Cancelled, "cancelled"},
{OrderStatus::Rejected, "rejected"}
})
NLOHMANN_JSON_SERIALIZE_ENUM(TimeInForce, {
{TimeInForce::GTC, "GTC"},
{TimeInForce::IOC, "IOC"},
{TimeInForce::FOK, "FOK"},
{TimeInForce::DAY, "DAY"}
})
inline void to_json(nlohmann::json& j, const Order& o) {
j = nlohmann::json{
{"orderId", o.order_id},
{"symbol", o.symbol},
{"type", o.type},
{"side", o.side},
{"price", o.price},
{"size", o.size},
{"filled", o.filled},
{"remaining", o.remaining},
{"status", o.status},
{"userId", o.user_id},
{"clientId", o.client_id},
{"timestamp", o.timestamp},
{"timeInForce", o.time_in_force},
{"postOnly", o.post_only},
{"reduceOnly", o.reduce_only}
};
}
inline void from_json(const nlohmann::json& j, Order& o) {
if (j.contains("orderId")) j.at("orderId").get_to(o.order_id);
if (j.contains("symbol")) j.at("symbol").get_to(o.symbol);
if (j.contains("type")) j.at("type").get_to(o.type);
if (j.contains("side")) j.at("side").get_to(o.side);
if (j.contains("price")) j.at("price").get_to(o.price);
if (j.contains("size")) j.at("size").get_to(o.size);
if (j.contains("filled")) j.at("filled").get_to(o.filled);
if (j.contains("remaining")) j.at("remaining").get_to(o.remaining);
if (j.contains("status")) j.at("status").get_to(o.status);
if (j.contains("userId")) j.at("userId").get_to(o.user_id);
if (j.contains("clientId")) j.at("clientId").get_to(o.client_id);
if (j.contains("timestamp")) j.at("timestamp").get_to(o.timestamp);
if (j.contains("timeInForce")) j.at("timeInForce").get_to(o.time_in_force);
if (j.contains("postOnly")) j.at("postOnly").get_to(o.post_only);
if (j.contains("reduceOnly")) j.at("reduceOnly").get_to(o.reduce_only);
}
inline void to_json(nlohmann::json& j, const Trade& t) {
j = nlohmann::json{
{"tradeId", t.trade_id},
{"symbol", t.symbol},
{"price", t.price},
{"size", t.size},
{"side", t.side},
{"buyOrderId", t.buy_order_id},
{"sellOrderId", t.sell_order_id},
{"buyerId", t.buyer_id},
{"sellerId", t.seller_id},
{"timestamp", t.timestamp}
};
}
inline void from_json(const nlohmann::json& j, Trade& t) {
if (j.contains("tradeId")) j.at("tradeId").get_to(t.trade_id);
if (j.contains("symbol")) j.at("symbol").get_to(t.symbol);
if (j.contains("price")) j.at("price").get_to(t.price);
if (j.contains("size")) j.at("size").get_to(t.size);
if (j.contains("side")) j.at("side").get_to(t.side);
if (j.contains("buyOrderId")) j.at("buyOrderId").get_to(t.buy_order_id);
if (j.contains("sellOrderId")) j.at("sellOrderId").get_to(t.sell_order_id);
if (j.contains("buyerId")) j.at("buyerId").get_to(t.buyer_id);
if (j.contains("sellerId")) j.at("sellerId").get_to(t.seller_id);
if (j.contains("timestamp")) j.at("timestamp").get_to(t.timestamp);
}
inline void to_json(nlohmann::json& j, const PriceLevel& p) {
j = nlohmann::json{{"price", p.price}, {"size", p.size}, {"count", p.count}};
}
inline void from_json(const nlohmann::json& j, PriceLevel& p) {
if (j.contains("price")) j.at("price").get_to(p.price);
if (j.contains("size")) j.at("size").get_to(p.size);
if (j.contains("count")) j.at("count").get_to(p.count);
}
inline void to_json(nlohmann::json& j, const OrderBook& ob) {
j = nlohmann::json{
{"symbol", ob.symbol},
{"bids", ob.bids},
{"asks", ob.asks},
{"timestamp", ob.timestamp}
};
}
inline void from_json(const nlohmann::json& j, OrderBook& ob) {
if (j.contains("symbol")) j.at("symbol").get_to(ob.symbol);
if (j.contains("bids")) j.at("bids").get_to(ob.bids);
if (j.contains("asks")) j.at("asks").get_to(ob.asks);
if (j.contains("timestamp")) j.at("timestamp").get_to(ob.timestamp);
}
inline void from_json(const nlohmann::json& j, OrderResponse& r) {
if (j.contains("orderId")) j.at("orderId").get_to(r.order_id);
if (j.contains("status")) j.at("status").get_to(r.status);
if (j.contains("message")) j.at("message").get_to(r.message);
}
inline void from_json(const nlohmann::json& j, Balance& b) {
if (j.contains("asset")) j.at("asset").get_to(b.asset);
if (j.contains("available")) j.at("available").get_to(b.available);
if (j.contains("locked")) j.at("locked").get_to(b.locked);
if (j.contains("total")) j.at("total").get_to(b.total);
}
inline void from_json(const nlohmann::json& j, Position& p) {
if (j.contains("symbol")) j.at("symbol").get_to(p.symbol);
if (j.contains("size")) j.at("size").get_to(p.size);
if (j.contains("entryPrice")) j.at("entryPrice").get_to(p.entry_price);
if (j.contains("markPrice")) j.at("markPrice").get_to(p.mark_price);
if (j.contains("pnl")) j.at("pnl").get_to(p.pnl);
if (j.contains("margin")) j.at("margin").get_to(p.margin);
}
inline void from_json(const nlohmann::json& j, NodeInfo& n) {
if (j.contains("version")) j.at("version").get_to(n.version);
if (j.contains("network")) j.at("network").get_to(n.network);
if (j.contains("orderCount")) j.at("orderCount").get_to(n.order_count);
if (j.contains("tradeCount")) j.at("tradeCount").get_to(n.trade_count);
if (j.contains("timestamp")) j.at("timestamp").get_to(n.timestamp);
if (j.contains("blockHeight")) j.at("blockHeight").get_to(n.block_height);
if (j.contains("syncing")) j.at("syncing").get_to(n.syncing);
if (j.contains("uptime")) j.at("uptime").get_to(n.uptime);
}
} // namespace lxdex
#endif // LXDEX_TYPES_HPP
-994
View File
@@ -1,994 +0,0 @@
// LX C++ SDK - Client Implementation
// Copyright (c) 2025 Lux Partners Limited
// SPDX-License-Identifier: MIT
#include "lxdex/client.hpp"
#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/client.hpp>
#include <thread>
#include <queue>
#include <condition_variable>
#include <random>
#include <sstream>
#include <iomanip>
namespace lxdex {
using WsClient = websocketpp::client<websocketpp::config::asio_client>;
using ConnectionHdl = websocketpp::connection_hdl;
using MessagePtr = websocketpp::config::asio_client::message_type::ptr;
//------------------------------------------------------------------------------
// Client::Impl
//------------------------------------------------------------------------------
class Client::Impl {
public:
explicit Impl(ClientConfig config)
: config_(std::move(config))
, state_(ConnectionState::Disconnected)
, authenticated_(false)
, request_id_(0)
, running_(false)
{
ws_client_.clear_access_channels(websocketpp::log::alevel::all);
ws_client_.clear_error_channels(websocketpp::log::elevel::all);
ws_client_.init_asio();
ws_client_.set_open_handler([this](ConnectionHdl hdl) {
on_open(hdl);
});
ws_client_.set_close_handler([this](ConnectionHdl hdl) {
on_close(hdl);
});
ws_client_.set_fail_handler([this](ConnectionHdl hdl) {
on_fail(hdl);
});
ws_client_.set_message_handler([this](ConnectionHdl hdl, MessagePtr msg) {
on_message(hdl, msg);
});
}
~Impl() {
disconnect();
}
Error connect() {
if (state_ == ConnectionState::Connected) {
return {};
}
state_ = ConnectionState::Connecting;
websocketpp::lib::error_code ec;
auto con = ws_client_.get_connection(config_.ws_url, ec);
if (ec) {
state_ = ConnectionState::Failed;
return Error{-1, "Failed to create connection: " + ec.message()};
}
connection_ = con->get_handle();
ws_client_.connect(con);
// Start IO thread
running_ = true;
io_thread_ = std::thread([this]() {
ws_client_.run();
});
// Wait for connection with timeout
{
std::unique_lock<std::mutex> lock(connect_mutex_);
auto result = connect_cv_.wait_for(
lock,
config_.connect_timeout,
[this]() {
return state_ == ConnectionState::Connected ||
state_ == ConnectionState::Failed;
}
);
if (!result) {
state_ = ConnectionState::Failed;
return Error{-2, "Connection timeout"};
}
}
if (state_ != ConnectionState::Connected) {
return Error{-3, "Connection failed"};
}
// Notify callback
if (connection_callback_) {
connection_callback_(state_);
}
return {};
}
void disconnect() {
if (state_ == ConnectionState::Disconnected) {
return;
}
running_ = false;
state_ = ConnectionState::Disconnected;
authenticated_ = false;
try {
if (!connection_.expired()) {
ws_client_.close(connection_, websocketpp::close::status::normal, "");
}
} catch (...) {
// Ignore close errors
}
ws_client_.stop();
if (io_thread_.joinable()) {
io_thread_.join();
}
// Notify callback
if (connection_callback_) {
connection_callback_(state_);
}
}
bool is_connected() const noexcept {
return state_ == ConnectionState::Connected;
}
ConnectionState state() const noexcept {
return state_;
}
Error authenticate() {
if (!is_connected()) {
return Error{-1, "Not connected"};
}
if (config_.api_key.empty() || config_.api_secret.empty()) {
return Error{-2, "API credentials not configured"};
}
nlohmann::json msg = {
{"type", "auth"},
{"apiKey", config_.api_key},
{"apiSecret", config_.api_secret},
{"request_id", next_request_id()}
};
auto result = send_and_wait(msg, std::chrono::seconds(10));
if (!result.ok()) {
return result.error;
}
if (result.value.contains("type") && result.value["type"] == "auth_success") {
authenticated_ = true;
return {};
}
return Error{-3, "Authentication failed"};
}
bool is_authenticated() const noexcept {
return authenticated_;
}
Result<OrderResponse> place_order(const Order& order) {
if (!is_connected()) {
return {{}, Error{-1, "Not connected"}};
}
if (!is_authenticated()) {
return {{}, Error{-2, "Not authenticated"}};
}
nlohmann::json order_data = {
{"symbol", order.symbol},
{"type", order.type},
{"side", order.side},
{"price", order.price},
{"size", order.size}
};
if (!order.client_id.empty()) {
order_data["clientId"] = order.client_id;
}
if (order.time_in_force != TimeInForce::GTC) {
order_data["timeInForce"] = order.time_in_force;
}
if (order.post_only) {
order_data["postOnly"] = true;
}
if (order.reduce_only) {
order_data["reduceOnly"] = true;
}
std::string req_id = next_request_id();
nlohmann::json msg = {
{"type", "place_order"},
{"order", order_data},
{"request_id", req_id}
};
auto result = send_and_wait(msg, std::chrono::seconds(10));
if (!result.ok()) {
return {{}, result.error};
}
metrics_.orders_sent++;
OrderResponse resp;
if (result.value.contains("data")) {
auto& data = result.value["data"];
if (data.contains("order") && data["order"].contains("ID")) {
resp.order_id = data["order"]["ID"].get<uint64_t>();
}
if (data.contains("status")) {
resp.status = data["status"].get<std::string>();
}
}
return {resp, {}};
}
std::future<Result<OrderResponse>> place_order_async(const Order& order) {
return std::async(std::launch::async, [this, order]() {
return place_order(order);
});
}
Error cancel_order(uint64_t order_id) {
if (!is_connected()) {
return Error{-1, "Not connected"};
}
if (!is_authenticated()) {
return Error{-2, "Not authenticated"};
}
nlohmann::json msg = {
{"type", "cancel_order"},
{"orderID", order_id},
{"request_id", next_request_id()}
};
auto result = send_and_wait(msg, std::chrono::seconds(10));
if (!result.ok()) {
return result.error;
}
return {};
}
std::future<Error> cancel_order_async(uint64_t order_id) {
return std::async(std::launch::async, [this, order_id]() {
return cancel_order(order_id);
});
}
Error modify_order(uint64_t order_id, double new_price, double new_size) {
if (!is_connected()) {
return Error{-1, "Not connected"};
}
if (!is_authenticated()) {
return Error{-2, "Not authenticated"};
}
nlohmann::json msg = {
{"type", "modify_order"},
{"orderID", order_id},
{"request_id", next_request_id()}
};
if (new_price > 0) {
msg["newPrice"] = new_price;
}
if (new_size > 0) {
msg["newSize"] = new_size;
}
auto result = send_and_wait(msg, std::chrono::seconds(10));
return result.error;
}
Result<int> cancel_all_orders(const std::string& symbol) {
// Cancel all would need to get orders first, then cancel each
auto orders_result = get_orders();
if (!orders_result.ok()) {
return {0, orders_result.error};
}
int cancelled = 0;
for (const auto& order : orders_result.value) {
if (symbol.empty() || order.symbol == symbol) {
auto err = cancel_order(order.order_id);
if (!err) {
cancelled++;
}
}
}
return {cancelled, {}};
}
Error subscribe_orderbook(
const std::vector<std::string>& symbols,
OrderBookCallback callback
) {
orderbook_callback_ = std::move(callback);
return subscribe("orderbook", symbols);
}
Error subscribe_trades(
const std::vector<std::string>& symbols,
TradeCallback callback
) {
trade_callback_ = std::move(callback);
return subscribe("trades", symbols);
}
Error subscribe(
const std::string& channel,
const std::vector<std::string>& symbols
) {
if (!is_connected()) {
return Error{-1, "Not connected"};
}
nlohmann::json msg = {
{"type", "subscribe"},
{"channel", channel},
{"symbols", symbols},
{"request_id", next_request_id()}
};
return send(msg);
}
Error unsubscribe(
const std::string& channel,
const std::vector<std::string>& symbols
) {
if (!is_connected()) {
return Error{-1, "Not connected"};
}
nlohmann::json msg = {
{"type", "unsubscribe"},
{"channel", channel},
{"symbols", symbols},
{"request_id", next_request_id()}
};
return send(msg);
}
Result<std::vector<Balance>> get_balances() {
if (!is_connected()) {
return {{}, Error{-1, "Not connected"}};
}
if (!is_authenticated()) {
return {{}, Error{-2, "Not authenticated"}};
}
nlohmann::json msg = {
{"type", "get_balances"},
{"request_id", next_request_id()}
};
auto result = send_and_wait(msg, std::chrono::seconds(10));
if (!result.ok()) {
return {{}, result.error};
}
std::vector<Balance> balances;
if (result.value.contains("data") && result.value["data"].contains("balances")) {
auto& bal_data = result.value["data"]["balances"];
for (auto& [asset, amount] : bal_data.items()) {
Balance b;
b.asset = asset;
if (amount.is_string()) {
b.available = std::stod(amount.get<std::string>());
} else {
b.available = amount.get<double>();
}
b.total = b.available;
balances.push_back(b);
}
}
return {balances, {}};
}
Result<std::vector<Position>> get_positions() {
if (!is_connected()) {
return {{}, Error{-1, "Not connected"}};
}
if (!is_authenticated()) {
return {{}, Error{-2, "Not authenticated"}};
}
nlohmann::json msg = {
{"type", "get_positions"},
{"request_id", next_request_id()}
};
auto result = send_and_wait(msg, std::chrono::seconds(10));
if (!result.ok()) {
return {{}, result.error};
}
std::vector<Position> positions;
if (result.value.contains("data") && result.value["data"].contains("positions")) {
for (auto& pos_data : result.value["data"]["positions"]) {
Position p;
if (pos_data.contains("symbol")) p.symbol = pos_data["symbol"];
if (pos_data.contains("size")) p.size = pos_data["size"];
if (pos_data.contains("entryPrice")) p.entry_price = pos_data["entryPrice"];
if (pos_data.contains("markPrice")) p.mark_price = pos_data["markPrice"];
if (pos_data.contains("pnl")) p.pnl = pos_data["pnl"];
if (pos_data.contains("margin")) p.margin = pos_data["margin"];
positions.push_back(p);
}
}
return {positions, {}};
}
Result<std::vector<Order>> get_orders() {
if (!is_connected()) {
return {{}, Error{-1, "Not connected"}};
}
if (!is_authenticated()) {
return {{}, Error{-2, "Not authenticated"}};
}
nlohmann::json msg = {
{"type", "get_orders"},
{"request_id", next_request_id()}
};
auto result = send_and_wait(msg, std::chrono::seconds(10));
if (!result.ok()) {
return {{}, result.error};
}
std::vector<Order> orders;
if (result.value.contains("data") && result.value["data"].contains("orders")) {
for (auto& order_data : result.value["data"]["orders"]) {
Order o = order_data.get<Order>();
orders.push_back(o);
}
}
return {orders, {}};
}
Result<OrderBook> get_orderbook(const std::string& symbol, int32_t depth) {
// Return from local cache if available
auto* book = orderbook_manager_.get(symbol);
if (book) {
return {book->get_snapshot(depth), {}};
}
// Otherwise subscribe and wait for first snapshot
Error err = subscribe("orderbook", {symbol});
if (err) {
return {{}, err};
}
// Wait briefly for snapshot
std::this_thread::sleep_for(std::chrono::milliseconds(500));
book = orderbook_manager_.get(symbol);
if (book) {
return {book->get_snapshot(depth), {}};
}
return {{}, Error{-1, "Failed to get orderbook"}};
}
Result<std::vector<Trade>> get_trades(const std::string& symbol, int32_t limit) {
return {trade_tracker_.get_by_symbol(symbol, limit), {}};
}
Result<NodeInfo> get_info() {
if (!is_connected()) {
return {{}, Error{-1, "Not connected"}};
}
nlohmann::json msg = {
{"type", "ping"},
{"request_id", next_request_id()}
};
auto result = send_and_wait(msg, std::chrono::seconds(5));
if (!result.ok()) {
return {{}, result.error};
}
NodeInfo info;
info.timestamp = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count();
return {info, {}};
}
Result<int64_t> ping() {
if (!is_connected()) {
return {0, Error{-1, "Not connected"}};
}
auto start = std::chrono::high_resolution_clock::now();
nlohmann::json msg = {
{"type", "ping"},
{"request_id", next_request_id()}
};
auto result = send_and_wait(msg, std::chrono::seconds(5));
if (!result.ok()) {
return {0, result.error};
}
auto end = std::chrono::high_resolution_clock::now();
auto latency = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
metrics_.last_latency_us = latency;
return {latency, {}};
}
void on_error(ErrorCallback callback) {
error_callback_ = std::move(callback);
}
void on_order(OrderCallback callback) {
order_callback_ = std::move(callback);
}
void on_trade(TradeCallback callback) {
trade_callback_ = std::move(callback);
}
void on_connection(ConnectionCallback callback) {
connection_callback_ = std::move(callback);
}
void on_message(MessageCallback callback) {
message_callback_ = std::move(callback);
}
OrderBookManager& orderbooks() noexcept {
return orderbook_manager_;
}
OrderTracker& orders() noexcept {
return order_tracker_;
}
TradeTracker& trades() noexcept {
return trade_tracker_;
}
ClientMetrics metrics() const noexcept {
return metrics_;
}
void reset_metrics() {
metrics_ = {};
}
static std::string generate_client_id() {
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_int_distribution<uint64_t> dis;
std::stringstream ss;
ss << "cpp_" << std::hex << std::setfill('0') << std::setw(16) << dis(gen);
return ss.str();
}
private:
void on_open(ConnectionHdl hdl) {
state_ = ConnectionState::Connected;
connect_cv_.notify_all();
}
void on_close(ConnectionHdl hdl) {
state_ = ConnectionState::Disconnected;
authenticated_ = false;
connect_cv_.notify_all();
if (connection_callback_) {
connection_callback_(state_);
}
// Auto reconnect
if (config_.auto_reconnect && running_) {
attempt_reconnect();
}
}
void on_fail(ConnectionHdl hdl) {
state_ = ConnectionState::Failed;
connect_cv_.notify_all();
if (connection_callback_) {
connection_callback_(state_);
}
}
void on_message(ConnectionHdl hdl, MessagePtr msg) {
metrics_.messages_received++;
try {
auto json = nlohmann::json::parse(msg->get_payload());
// Handle request responses
if (json.contains("request_id")) {
std::string req_id = json["request_id"];
std::lock_guard<std::mutex> lock(pending_mutex_);
auto it = pending_requests_.find(req_id);
if (it != pending_requests_.end()) {
it->second.set_value(json);
pending_requests_.erase(it);
}
}
// Handle specific message types
if (json.contains("type")) {
std::string type = json["type"];
if (type == "orderbook_update" && json.contains("data")) {
handle_orderbook_update(json["data"]);
} else if (type == "trade_update" && json.contains("data")) {
handle_trade_update(json["data"]);
} else if (type == "order_update" && json.contains("data")) {
handle_order_update(json["data"]);
} else if (type == "error") {
handle_error(json);
}
}
// Raw message callback
if (message_callback_) {
Message m;
if (json.contains("type")) m.type = json["type"];
if (json.contains("data")) m.data = json["data"];
if (json.contains("error")) m.error = json["error"];
if (json.contains("request_id")) m.request_id = json["request_id"];
if (json.contains("timestamp")) m.timestamp = json["timestamp"];
message_callback_(m);
}
} catch (const std::exception& e) {
metrics_.error_count++;
if (error_callback_) {
error_callback_(Error{-1, std::string("JSON parse error: ") + e.what()});
}
}
}
void handle_orderbook_update(const nlohmann::json& data) {
if (!data.contains("symbol")) return;
std::string symbol = data["symbol"];
auto& book = orderbook_manager_.get_or_create(symbol);
if (data.contains("snapshot")) {
OrderBook snapshot = data["snapshot"].get<OrderBook>();
snapshot.symbol = symbol;
book.apply_snapshot(snapshot);
}
if (orderbook_callback_) {
orderbook_callback_(book.get_snapshot());
}
}
void handle_trade_update(const nlohmann::json& data) {
if (data.contains("trade")) {
Trade trade = data["trade"].get<Trade>();
trade_tracker_.add(trade);
metrics_.trades_received++;
if (trade_callback_) {
trade_callback_(trade);
}
}
}
void handle_order_update(const nlohmann::json& data) {
if (data.contains("order")) {
Order order = data["order"].get<Order>();
order_tracker_.upsert(order);
if (order_callback_) {
order_callback_(order);
}
}
}
void handle_error(const nlohmann::json& json) {
metrics_.error_count++;
if (error_callback_) {
Error err;
if (json.contains("error")) err.message = json["error"];
if (json.contains("request_id")) err.request_id = json["request_id"];
err.code = -1;
error_callback_(err);
}
}
void attempt_reconnect() {
if (metrics_.reconnect_count >= static_cast<uint64_t>(config_.max_reconnect_attempts)) {
state_ = ConnectionState::Failed;
return;
}
state_ = ConnectionState::Reconnecting;
metrics_.reconnect_count++;
std::this_thread::sleep_for(config_.reconnect_delay);
ws_client_.reset();
ws_client_.init_asio();
connect();
}
Error send(const nlohmann::json& msg) {
if (!is_connected()) {
return Error{-1, "Not connected"};
}
try {
std::string payload = msg.dump();
ws_client_.send(connection_, payload, websocketpp::frame::opcode::text);
metrics_.messages_sent++;
return {};
} catch (const std::exception& e) {
return Error{-1, std::string("Send failed: ") + e.what()};
}
}
Result<nlohmann::json> send_and_wait(
const nlohmann::json& msg,
std::chrono::seconds timeout
) {
std::string req_id = msg["request_id"];
std::promise<nlohmann::json> promise;
auto future = promise.get_future();
{
std::lock_guard<std::mutex> lock(pending_mutex_);
pending_requests_[req_id] = std::move(promise);
}
auto send_err = send(msg);
if (send_err) {
std::lock_guard<std::mutex> lock(pending_mutex_);
pending_requests_.erase(req_id);
return {{}, send_err};
}
auto status = future.wait_for(timeout);
if (status == std::future_status::timeout) {
std::lock_guard<std::mutex> lock(pending_mutex_);
pending_requests_.erase(req_id);
return {{}, Error{-2, "Request timeout"}};
}
try {
return {future.get(), {}};
} catch (const std::exception& e) {
return {{}, Error{-3, std::string("Request failed: ") + e.what()}};
}
}
std::string next_request_id() {
return std::to_string(++request_id_);
}
ClientConfig config_;
WsClient ws_client_;
ConnectionHdl connection_;
std::thread io_thread_;
std::atomic<ConnectionState> state_;
std::atomic<bool> authenticated_;
std::atomic<uint64_t> request_id_;
std::atomic<bool> running_;
std::mutex connect_mutex_;
std::condition_variable connect_cv_;
std::mutex pending_mutex_;
std::unordered_map<std::string, std::promise<nlohmann::json>> pending_requests_;
// Callbacks
ErrorCallback error_callback_;
OrderCallback order_callback_;
TradeCallback trade_callback_;
OrderBookCallback orderbook_callback_;
ConnectionCallback connection_callback_;
MessageCallback message_callback_;
// Local data
OrderBookManager orderbook_manager_;
OrderTracker order_tracker_;
TradeTracker trade_tracker_;
// Metrics
mutable ClientMetrics metrics_;
};
//------------------------------------------------------------------------------
// Client public interface
//------------------------------------------------------------------------------
Client::Client(ClientConfig config)
: impl_(std::make_unique<Impl>(std::move(config)))
{}
Client::~Client() = default;
Error Client::connect() {
return impl_->connect();
}
void Client::disconnect() {
impl_->disconnect();
}
bool Client::is_connected() const noexcept {
return impl_->is_connected();
}
ConnectionState Client::state() const noexcept {
return impl_->state();
}
Error Client::authenticate() {
return impl_->authenticate();
}
bool Client::is_authenticated() const noexcept {
return impl_->is_authenticated();
}
Result<OrderResponse> Client::place_order(const Order& order) {
return impl_->place_order(order);
}
std::future<Result<OrderResponse>> Client::place_order_async(const Order& order) {
return impl_->place_order_async(order);
}
Error Client::cancel_order(uint64_t order_id) {
return impl_->cancel_order(order_id);
}
std::future<Error> Client::cancel_order_async(uint64_t order_id) {
return impl_->cancel_order_async(order_id);
}
Error Client::modify_order(uint64_t order_id, double new_price, double new_size) {
return impl_->modify_order(order_id, new_price, new_size);
}
Result<int> Client::cancel_all_orders(const std::string& symbol) {
return impl_->cancel_all_orders(symbol);
}
Result<OrderBook> Client::get_orderbook(const std::string& symbol, int32_t depth) {
return impl_->get_orderbook(symbol, depth);
}
Result<std::vector<Trade>> Client::get_trades(const std::string& symbol, int32_t limit) {
return impl_->get_trades(symbol, limit);
}
Error Client::subscribe_orderbook(
const std::vector<std::string>& symbols,
OrderBookCallback callback
) {
return impl_->subscribe_orderbook(symbols, std::move(callback));
}
Error Client::subscribe_trades(
const std::vector<std::string>& symbols,
TradeCallback callback
) {
return impl_->subscribe_trades(symbols, std::move(callback));
}
Error Client::unsubscribe(
const std::string& channel,
const std::vector<std::string>& symbols
) {
return impl_->unsubscribe(channel, symbols);
}
Result<std::vector<Balance>> Client::get_balances() {
return impl_->get_balances();
}
Result<std::vector<Position>> Client::get_positions() {
return impl_->get_positions();
}
Result<std::vector<Order>> Client::get_orders() {
return impl_->get_orders();
}
Result<NodeInfo> Client::get_info() {
return impl_->get_info();
}
void Client::on_error(ErrorCallback callback) {
impl_->on_error(std::move(callback));
}
void Client::on_order(OrderCallback callback) {
impl_->on_order(std::move(callback));
}
void Client::on_trade(TradeCallback callback) {
impl_->on_trade(std::move(callback));
}
void Client::on_connection(ConnectionCallback callback) {
impl_->on_connection(std::move(callback));
}
void Client::on_message(MessageCallback callback) {
impl_->on_message(std::move(callback));
}
OrderBookManager& Client::orderbooks() noexcept {
return impl_->orderbooks();
}
OrderTracker& Client::orders() noexcept {
return impl_->orders();
}
TradeTracker& Client::trades() noexcept {
return impl_->trades();
}
ClientMetrics Client::metrics() const noexcept {
return impl_->metrics();
}
void Client::reset_metrics() {
impl_->reset_metrics();
}
Result<int64_t> Client::ping() {
return impl_->ping();
}
std::string Client::generate_client_id() {
return Impl::generate_client_id();
}
} // namespace lxdex
-361
View File
@@ -1,361 +0,0 @@
// LX C++ SDK - Orderbook Implementation
// Copyright (c) 2025 Lux Partners Limited
// SPDX-License-Identifier: MIT
#include "lxdex/orderbook.hpp"
#include <algorithm>
#include <chrono>
namespace lxdex {
//------------------------------------------------------------------------------
// LocalOrderBook
//------------------------------------------------------------------------------
LocalOrderBook::LocalOrderBook(std::string symbol)
: symbol_(std::move(symbol))
{}
void LocalOrderBook::apply_snapshot(const OrderBook& snapshot) {
std::lock_guard<std::mutex> lock(mutex_);
bids_.clear();
asks_.clear();
for (const auto& level : snapshot.bids) {
if (level.size > 0) {
bids_[level.price] = level;
}
}
for (const auto& level : snapshot.asks) {
if (level.size > 0) {
asks_[level.price] = level;
}
}
last_update_.store(snapshot.timestamp > 0
? snapshot.timestamp
: std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()
).count()
);
notify_update();
}
void LocalOrderBook::update_level(Side side, double price, double size) {
std::lock_guard<std::mutex> lock(mutex_);
if (side == Side::Buy) {
if (size > 0) {
bids_[price] = PriceLevel{price, size, 1};
} else {
bids_.erase(price);
}
} else {
if (size > 0) {
asks_[price] = PriceLevel{price, size, 1};
} else {
asks_.erase(price);
}
}
last_update_.store(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()
).count());
notify_update();
}
void LocalOrderBook::remove_level(Side side, double price) {
update_level(side, price, 0);
}
OrderBook LocalOrderBook::get_snapshot(int32_t depth) const {
std::lock_guard<std::mutex> lock(mutex_);
OrderBook ob;
ob.symbol = symbol_;
ob.timestamp = last_update_.load();
size_t limit = depth > 0 ? static_cast<size_t>(depth) : SIZE_MAX;
ob.bids.reserve(std::min(bids_.size(), limit));
size_t count = 0;
for (const auto& [price, level] : bids_) {
if (count >= limit) break;
ob.bids.push_back(level);
++count;
}
ob.asks.reserve(std::min(asks_.size(), limit));
count = 0;
for (const auto& [price, level] : asks_) {
if (count >= limit) break;
ob.asks.push_back(level);
++count;
}
return ob;
}
std::optional<PriceLevel> LocalOrderBook::best_bid() const {
std::lock_guard<std::mutex> lock(mutex_);
if (bids_.empty()) return std::nullopt;
return bids_.begin()->second;
}
std::optional<PriceLevel> LocalOrderBook::best_ask() const {
std::lock_guard<std::mutex> lock(mutex_);
if (asks_.empty()) return std::nullopt;
return asks_.begin()->second;
}
double LocalOrderBook::mid_price() const {
std::lock_guard<std::mutex> lock(mutex_);
if (bids_.empty() || asks_.empty()) return 0.0;
return (bids_.begin()->first + asks_.begin()->first) / 2.0;
}
double LocalOrderBook::spread() const {
std::lock_guard<std::mutex> lock(mutex_);
if (bids_.empty() || asks_.empty()) return 0.0;
return asks_.begin()->first - bids_.begin()->first;
}
double LocalOrderBook::bid_depth() const {
std::lock_guard<std::mutex> lock(mutex_);
double total = 0.0;
for (const auto& [price, level] : bids_) {
total += level.size;
}
return total;
}
double LocalOrderBook::ask_depth() const {
std::lock_guard<std::mutex> lock(mutex_);
double total = 0.0;
for (const auto& [price, level] : asks_) {
total += level.size;
}
return total;
}
void LocalOrderBook::clear() {
std::lock_guard<std::mutex> lock(mutex_);
bids_.clear();
asks_.clear();
last_update_.store(0);
}
void LocalOrderBook::on_update(std::function<void(const OrderBook&)> callback) {
update_callback_ = std::move(callback);
}
void LocalOrderBook::notify_update() {
if (update_callback_) {
// Create snapshot without lock (already held by caller)
OrderBook ob;
ob.symbol = symbol_;
ob.timestamp = last_update_.load();
for (const auto& [price, level] : bids_) {
ob.bids.push_back(level);
}
for (const auto& [price, level] : asks_) {
ob.asks.push_back(level);
}
update_callback_(ob);
}
}
//------------------------------------------------------------------------------
// OrderTracker
//------------------------------------------------------------------------------
void OrderTracker::upsert(const Order& order) {
std::lock_guard<std::mutex> lock(mutex_);
orders_[order.order_id] = order;
}
bool OrderTracker::remove(uint64_t order_id) {
std::lock_guard<std::mutex> lock(mutex_);
return orders_.erase(order_id) > 0;
}
std::optional<Order> OrderTracker::get(uint64_t order_id) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = orders_.find(order_id);
if (it == orders_.end()) return std::nullopt;
return it->second;
}
std::vector<Order> OrderTracker::get_by_symbol(const std::string& symbol) const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<Order> result;
for (const auto& [id, order] : orders_) {
if (order.symbol == symbol) {
result.push_back(order);
}
}
return result;
}
std::vector<Order> OrderTracker::get_open() const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<Order> result;
for (const auto& [id, order] : orders_) {
if (order.is_open()) {
result.push_back(order);
}
}
return result;
}
std::vector<Order> OrderTracker::get_all() const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<Order> result;
result.reserve(orders_.size());
for (const auto& [id, order] : orders_) {
result.push_back(order);
}
return result;
}
void OrderTracker::clear() {
std::lock_guard<std::mutex> lock(mutex_);
orders_.clear();
}
size_t OrderTracker::count() const {
std::lock_guard<std::mutex> lock(mutex_);
return orders_.size();
}
//------------------------------------------------------------------------------
// TradeTracker
//------------------------------------------------------------------------------
TradeTracker::TradeTracker(size_t max_trades)
: max_trades_(max_trades)
{
trades_.reserve(max_trades_);
}
void TradeTracker::add(const Trade& trade) {
std::lock_guard<std::mutex> lock(mutex_);
trades_.push_back(trade);
// Trim if over limit
if (trades_.size() > max_trades_) {
trades_.erase(trades_.begin(), trades_.begin() + (trades_.size() - max_trades_));
}
if (trade_callback_) {
trade_callback_(trade);
}
}
std::vector<Trade> TradeTracker::get_by_symbol(
const std::string& symbol,
size_t limit
) const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<Trade> result;
result.reserve(std::min(limit, trades_.size()));
// Iterate in reverse (most recent first)
for (auto it = trades_.rbegin(); it != trades_.rend() && result.size() < limit; ++it) {
if (it->symbol == symbol) {
result.push_back(*it);
}
}
return result;
}
std::vector<Trade> TradeTracker::get_recent(size_t limit) const {
std::lock_guard<std::mutex> lock(mutex_);
size_t count = std::min(limit, trades_.size());
std::vector<Trade> result;
result.reserve(count);
// Get most recent trades
auto start = trades_.size() > limit ? trades_.end() - limit : trades_.begin();
for (auto it = trades_.rbegin(); it != trades_.rend() && result.size() < limit; ++it) {
result.push_back(*it);
}
return result;
}
void TradeTracker::clear() {
std::lock_guard<std::mutex> lock(mutex_);
trades_.clear();
}
size_t TradeTracker::count() const {
std::lock_guard<std::mutex> lock(mutex_);
return trades_.size();
}
void TradeTracker::on_trade(std::function<void(const Trade&)> callback) {
trade_callback_ = std::move(callback);
}
//------------------------------------------------------------------------------
// OrderBookManager
//------------------------------------------------------------------------------
LocalOrderBook& OrderBookManager::get_or_create(const std::string& symbol) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = books_.find(symbol);
if (it != books_.end()) {
return *it->second;
}
auto [new_it, inserted] = books_.emplace(
symbol,
std::make_unique<LocalOrderBook>(symbol)
);
return *new_it->second;
}
LocalOrderBook* OrderBookManager::get(const std::string& symbol) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = books_.find(symbol);
return it != books_.end() ? it->second.get() : nullptr;
}
bool OrderBookManager::has(const std::string& symbol) const {
std::lock_guard<std::mutex> lock(mutex_);
return books_.find(symbol) != books_.end();
}
bool OrderBookManager::remove(const std::string& symbol) {
std::lock_guard<std::mutex> lock(mutex_);
return books_.erase(symbol) > 0;
}
std::vector<std::string> OrderBookManager::symbols() const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::string> result;
result.reserve(books_.size());
for (const auto& [symbol, book] : books_) {
result.push_back(symbol);
}
return result;
}
void OrderBookManager::clear() {
std::lock_guard<std::mutex> lock(mutex_);
books_.clear();
}
} // namespace lxdex
-86
View File
@@ -1,86 +0,0 @@
cmake_minimum_required(VERSION 3.16)
project(lx_trading_c VERSION 1.0.0 LANGUAGES C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Options
option(LX_TRADING_C_BUILD_TESTS "Build tests" ON)
option(LX_TRADING_C_USE_SIMD "Enable SIMD optimizations" OFF)
# Compiler flags
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
if(LX_TRADING_C_USE_SIMD)
add_compile_options(-march=native)
endif()
endif()
# Find dependencies
find_package(Threads REQUIRED)
# Math library (required on Linux, optional on macOS/Windows)
find_library(MATH_LIBRARY m)
# Library sources
set(LX_TRADING_C_SOURCES
src/types.c
src/orderbook.c
src/risk.c
src/math.c
)
# Static library
add_library(lx_trading_c STATIC ${LX_TRADING_C_SOURCES})
target_include_directories(lx_trading_c
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(lx_trading_c
PUBLIC
Threads::Threads
)
if(MATH_LIBRARY)
target_link_libraries(lx_trading_c PUBLIC ${MATH_LIBRARY})
endif()
# Tests
if(LX_TRADING_C_BUILD_TESTS)
enable_testing()
# Test executable
add_executable(lx_trading_c_tests
tests/test_main.c
tests/test_types.c
tests/test_orderbook.c
tests/test_risk.c
tests/test_math.c
)
target_link_libraries(lx_trading_c_tests
PRIVATE
lx_trading_c
)
# Register tests with CTest
add_test(NAME types_test COMMAND lx_trading_c_tests types)
add_test(NAME orderbook_test COMMAND lx_trading_c_tests orderbook)
add_test(NAME risk_test COMMAND lx_trading_c_tests risk)
add_test(NAME math_test COMMAND lx_trading_c_tests math)
add_test(NAME all_tests COMMAND lx_trading_c_tests all)
endif()
# Install
include(GNUInstallDirs)
install(TARGETS lx_trading_c
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
-304
View File
@@ -1,304 +0,0 @@
/**
* LX Trading SDK - Financial Mathematics
* Options pricing, AMM math, and risk metrics
*/
#ifndef LX_TRADING_MATH_H
#define LX_TRADING_MATH_H
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ============================================================================
* Constants
* ============================================================================ */
#define LX_MATH_PI 3.14159265358979323846
#define LX_MATH_SQRT_2PI 2.506628274631000502
#define LX_MATH_SQRT_2 1.41421356237309504880
/* ============================================================================
* Greeks Structure
* ============================================================================ */
typedef struct {
double delta;
double gamma;
double theta;
double vega;
double rho;
} LxGreeks;
/* ============================================================================
* Statistical Functions
* ============================================================================ */
/* Standard normal CDF (Abramowitz & Stegun approximation) */
double lx_norm_cdf(double x);
/* Standard normal PDF */
double lx_norm_pdf(double x);
/* ============================================================================
* Black-Scholes Options Pricing
* ============================================================================ */
/**
* Calculate Black-Scholes option price
* @param S Spot price
* @param K Strike price
* @param T Time to expiration (years)
* @param r Risk-free rate
* @param sigma Volatility
* @param is_call true for call, false for put
* @return Option price
*/
double lx_black_scholes(double S, double K, double T, double r, double sigma, bool is_call);
/**
* Calculate implied volatility from option price (Newton-Raphson)
* @param price Option price
* @param S Spot price
* @param K Strike price
* @param T Time to expiration (years)
* @param r Risk-free rate
* @param is_call true for call, false for put
* @param tol Tolerance (default 1e-6)
* @param max_iter Maximum iterations (default 100)
* @return Implied volatility, or -1 if not found
*/
double lx_implied_volatility(double price, double S, double K, double T, double r,
bool is_call, double tol, int max_iter);
/**
* Calculate all Greeks
* @param S Spot price
* @param K Strike price
* @param T Time to expiration (years)
* @param r Risk-free rate
* @param sigma Volatility
* @param is_call true for call, false for put
* @return Greeks structure
*/
LxGreeks lx_greeks(double S, double K, double T, double r, double sigma, bool is_call);
/* ============================================================================
* AMM Pricing - Constant Product (Uniswap V2)
* ============================================================================ */
/**
* Calculate constant product AMM swap output
* @param reserve_x Reserve of token X
* @param reserve_y Reserve of token Y
* @param amount_in Amount of input token
* @param fee_rate Fee rate (e.g., 0.003 for 0.3%)
* @param is_x_to_y true if swapping X for Y, false for Y to X
* @param output_amount Output: amount of output token
* @param effective_price Output: effective price
*/
void lx_constant_product_price(double reserve_x, double reserve_y,
double amount_in, double fee_rate, bool is_x_to_y,
double* output_amount, double* effective_price);
/* ============================================================================
* AMM Pricing - Concentrated Liquidity (Uniswap V3)
* ============================================================================ */
/**
* Calculate concentrated liquidity swap output
* @param liquidity Liquidity in the range
* @param sqrt_price_current Current sqrt price
* @param sqrt_price_lower Lower sqrt price bound
* @param sqrt_price_upper Upper sqrt price bound
* @param amount_in Amount of input token
* @param fee_rate Fee rate
* @param is_token0_in true if token0 is input
* @param output_amount Output: amount of output token
* @param new_sqrt_price Output: new sqrt price after swap
* @param price_impact Output: price impact percentage
*/
void lx_concentrated_liquidity_price(double liquidity,
double sqrt_price_current,
double sqrt_price_lower,
double sqrt_price_upper,
double amount_in,
double fee_rate,
bool is_token0_in,
double* output_amount,
double* new_sqrt_price,
double* price_impact);
/**
* Calculate liquidity for a concentrated position
*/
double lx_calculate_liquidity(double amount_x, double amount_y,
double sqrt_price_current,
double sqrt_price_lower,
double sqrt_price_upper);
/* ============================================================================
* Price Conversions
* ============================================================================ */
double lx_price_to_sqrt_price(double price);
double lx_sqrt_price_to_price(double sqrt_price);
double lx_tick_to_sqrt_price(int tick);
int lx_sqrt_price_to_tick(double sqrt_price, int tick_spacing);
/* ============================================================================
* Risk Metrics
* ============================================================================ */
/**
* Calculate historical volatility
* @param returns Array of returns
* @param count Number of returns
* @param annualize Whether to annualize
* @param periods_per_year Periods per year (e.g., 252 for daily)
* @return Volatility
*/
double lx_volatility(const double* returns, size_t count,
bool annualize, int periods_per_year);
/**
* Calculate Sharpe ratio
* @param returns Array of returns
* @param count Number of returns
* @param risk_free_rate Risk-free rate (period, not annual)
* @param periods_per_year Periods per year
* @return Sharpe ratio
*/
double lx_sharpe_ratio(const double* returns, size_t count,
double risk_free_rate, int periods_per_year);
/**
* Calculate Sortino ratio (uses downside deviation)
* @param returns Array of returns
* @param count Number of returns
* @param risk_free_rate Risk-free rate
* @param target_return Target return (for downside calculation)
* @param periods_per_year Periods per year
* @return Sortino ratio
*/
double lx_sortino_ratio(const double* returns, size_t count,
double risk_free_rate, double target_return,
int periods_per_year);
/**
* Calculate maximum drawdown
* @param prices Array of prices
* @param count Number of prices
* @param peak_idx Output: index of peak
* @param trough_idx Output: index of trough
* @return Maximum drawdown as a fraction (0.0 to 1.0)
*/
double lx_max_drawdown(const double* prices, size_t count,
size_t* peak_idx, size_t* trough_idx);
/**
* Calculate Value at Risk
* @param returns Array of returns
* @param count Number of returns
* @param confidence Confidence level (e.g., 0.95)
* @param parametric Use parametric (true) or historical (false)
* @return VaR as positive number (loss)
*/
double lx_var(const double* returns, size_t count,
double confidence, bool parametric);
/**
* Calculate Conditional VaR (Expected Shortfall)
* @param returns Array of returns
* @param count Number of returns
* @param confidence Confidence level
* @return CVaR as positive number
*/
double lx_cvar(const double* returns, size_t count, double confidence);
/* ============================================================================
* Statistical Utilities
* ============================================================================ */
/**
* Calculate returns from prices
* @param prices Array of prices
* @param price_count Number of prices
* @param returns Output: array of returns (must have space for price_count - 1)
* @return Number of returns calculated
*/
size_t lx_calculate_returns(const double* prices, size_t price_count, double* returns);
/**
* Calculate rolling mean
* @param data Input data
* @param count Number of data points
* @param window Window size
* @param result Output: rolling means (must have space for count - window + 1)
* @return Number of results
*/
size_t lx_rolling_mean(const double* data, size_t count, size_t window, double* result);
/**
* Calculate rolling standard deviation
* @param data Input data
* @param count Number of data points
* @param window Window size
* @param result Output: rolling stds (must have space for count - window + 1)
* @return Number of results
*/
size_t lx_rolling_std(const double* data, size_t count, size_t window, double* result);
/**
* Calculate exponential moving average
* @param data Input data
* @param count Number of data points
* @param alpha Smoothing factor (0 < alpha <= 1)
* @param result Output: EMA values (must have space for count)
*/
void lx_ema(const double* data, size_t count, double alpha, double* result);
/**
* Calculate correlation coefficient
* @param x First array
* @param y Second array
* @param count Number of elements
* @return Correlation coefficient (-1 to 1)
*/
double lx_correlation(const double* x, const double* y, size_t count);
/**
* Calculate covariance
* @param x First array
* @param y Second array
* @param count Number of elements
* @return Covariance
*/
double lx_covariance(const double* x, const double* y, size_t count);
/**
* Calculate beta coefficient
* @param asset_returns Asset returns
* @param market_returns Market returns
* @param count Number of returns
* @return Beta coefficient
*/
double lx_beta(const double* asset_returns, const double* market_returns, size_t count);
/* ============================================================================
* Basic Statistics
* ============================================================================ */
double lx_mean(const double* data, size_t count);
double lx_variance(const double* data, size_t count);
double lx_std_dev(const double* data, size_t count);
double lx_sum(const double* data, size_t count);
#ifdef __cplusplus
}
#endif
#endif /* LX_TRADING_MATH_H */
@@ -1,220 +0,0 @@
/**
* LX Trading SDK - Orderbook
* Thread-safe orderbook with VWAP, slippage, and liquidity calculations
*/
#ifndef LX_TRADING_ORDERBOOK_H
#define LX_TRADING_ORDERBOOK_H
#include "types.h"
#include <pthread.h>
#include <stdatomic.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ============================================================================
* Orderbook
* ============================================================================
* Single-venue orderbook with thread-safe operations.
*/
typedef struct {
char symbol[LX_SYMBOL_MAX_LEN * 2 + 2];
char venue[LX_VENUE_MAX_LEN];
/* Atomic timestamp and sequence */
atomic_int_fast64_t timestamp;
atomic_uint_fast64_t sequence;
/* Price levels (dynamically allocated) */
LxPriceLevel* bids;
size_t bid_count;
size_t bid_capacity;
LxPriceLevel* asks;
size_t ask_count;
size_t ask_capacity;
/* Thread safety */
pthread_rwlock_t lock;
} LxOrderbook;
/* ============================================================================
* Orderbook Lifecycle
* ============================================================================ */
/* Initialize orderbook (returns 0 on success, -1 on error) */
int lx_orderbook_init(LxOrderbook* book, const char* symbol, const char* venue);
/* Cleanup orderbook (frees internal memory) */
void lx_orderbook_cleanup(LxOrderbook* book);
/* Create/destroy on heap */
LxOrderbook* lx_orderbook_create(const char* symbol, const char* venue);
void lx_orderbook_destroy(LxOrderbook* book);
/* ============================================================================
* Orderbook Accessors (thread-safe reads)
* ============================================================================ */
const char* lx_orderbook_symbol(const LxOrderbook* book);
const char* lx_orderbook_venue(const LxOrderbook* book);
int64_t lx_orderbook_timestamp(const LxOrderbook* book);
uint64_t lx_orderbook_sequence(const LxOrderbook* book);
/* ============================================================================
* Orderbook Mutators (thread-safe writes)
* ============================================================================ */
/* Add a price level (appends to list) */
int lx_orderbook_add_bid(LxOrderbook* book, LxDecimal price, LxDecimal quantity);
int lx_orderbook_add_ask(LxOrderbook* book, LxDecimal price, LxDecimal quantity);
/* Set a price level (updates existing or adds new) */
int lx_orderbook_set_bid(LxOrderbook* book, LxDecimal price, LxDecimal quantity);
int lx_orderbook_set_ask(LxOrderbook* book, LxDecimal price, LxDecimal quantity);
/* Remove a price level */
void lx_orderbook_remove_bid(LxOrderbook* book, LxDecimal price);
void lx_orderbook_remove_ask(LxOrderbook* book, LxDecimal price);
/* Clear all levels */
void lx_orderbook_clear(LxOrderbook* book);
/* Sort levels (bids descending, asks ascending) and update timestamp/sequence */
void lx_orderbook_sort(LxOrderbook* book);
/* Set timestamp/sequence directly */
void lx_orderbook_set_timestamp(LxOrderbook* book, int64_t ts);
void lx_orderbook_set_sequence(LxOrderbook* book, uint64_t seq);
/* ============================================================================
* Orderbook Readers (thread-safe snapshots)
* ============================================================================ */
/* Get copies of price levels (caller must free returned array) */
LxPriceLevel* lx_orderbook_bids_copy(const LxOrderbook* book, size_t* count);
LxPriceLevel* lx_orderbook_asks_copy(const LxOrderbook* book, size_t* count);
/* Get best bid/ask (returns false if empty) */
bool lx_orderbook_best_bid(const LxOrderbook* book, LxDecimal* price);
bool lx_orderbook_best_ask(const LxOrderbook* book, LxDecimal* price);
/* Get mid price (returns false if no bid or ask) */
bool lx_orderbook_mid_price(const LxOrderbook* book, LxDecimal* price);
/* Get spread (returns false if no bid or ask) */
bool lx_orderbook_spread(const LxOrderbook* book, LxDecimal* spread);
bool lx_orderbook_spread_percent(const LxOrderbook* book, LxDecimal* spread_pct);
/* ============================================================================
* Liquidity Calculations
* ============================================================================ */
/* Total notional liquidity on each side */
LxDecimal lx_orderbook_bid_liquidity(const LxOrderbook* book);
LxDecimal lx_orderbook_ask_liquidity(const LxOrderbook* book);
/* Notional depth for N levels */
LxDecimal lx_orderbook_bid_depth(const LxOrderbook* book, int levels);
LxDecimal lx_orderbook_ask_depth(const LxOrderbook* book, int levels);
/* Check if sufficient liquidity exists */
bool lx_orderbook_has_liquidity(const LxOrderbook* book, LxSide side, LxDecimal amount);
/* ============================================================================
* VWAP Calculations
* ============================================================================ */
/* Volume-weighted average price for buying amount (walks asks) */
bool lx_orderbook_vwap_buy(const LxOrderbook* book, LxDecimal amount, LxDecimal* vwap);
/* Volume-weighted average price for selling amount (walks bids) */
bool lx_orderbook_vwap_sell(const LxOrderbook* book, LxDecimal amount, LxDecimal* vwap);
/* ============================================================================
* Slippage Calculation
* ============================================================================ */
/* Calculate slippage for a given order size
* Returns the difference between VWAP and best price as a percentage */
bool lx_orderbook_slippage_buy(const LxOrderbook* book, LxDecimal amount, LxDecimal* slippage_pct);
bool lx_orderbook_slippage_sell(const LxOrderbook* book, LxDecimal amount, LxDecimal* slippage_pct);
/* ============================================================================
* Aggregated Orderbook
* ============================================================================
* Combines orderbooks from multiple venues.
*/
typedef struct {
char venue[LX_VENUE_MAX_LEN];
LxDecimal quantity;
} LxVenueQuantity;
typedef struct {
LxDecimal price;
LxVenueQuantity* venues;
size_t venue_count;
size_t venue_capacity;
} LxAggregatedLevel;
typedef struct {
char symbol[LX_SYMBOL_MAX_LEN * 2 + 2];
int64_t timestamp;
LxAggregatedLevel* bids;
size_t bid_count;
size_t bid_capacity;
LxAggregatedLevel* asks;
size_t ask_count;
size_t ask_capacity;
} LxAggregatedOrderbook;
/* ============================================================================
* Aggregated Orderbook Lifecycle
* ============================================================================ */
int lx_aggregated_orderbook_init(LxAggregatedOrderbook* agg, const char* symbol);
void lx_aggregated_orderbook_cleanup(LxAggregatedOrderbook* agg);
LxAggregatedOrderbook* lx_aggregated_orderbook_create(const char* symbol);
void lx_aggregated_orderbook_destroy(LxAggregatedOrderbook* agg);
/* ============================================================================
* Aggregated Orderbook Operations
* ============================================================================ */
/* Add an orderbook to the aggregation */
int lx_aggregated_orderbook_add(LxAggregatedOrderbook* agg, const LxOrderbook* book);
/* Clear all aggregated data */
void lx_aggregated_orderbook_clear(LxAggregatedOrderbook* agg);
/* Best bid across all venues (price, venue, qty) */
bool lx_aggregated_orderbook_best_bid(const LxAggregatedOrderbook* agg,
LxDecimal* price, char* venue, size_t venue_buf_size,
LxDecimal* quantity);
/* Best ask across all venues (price, venue, qty) */
bool lx_aggregated_orderbook_best_ask(const LxAggregatedOrderbook* agg,
LxDecimal* price, char* venue, size_t venue_buf_size,
LxDecimal* quantity);
/* Get aggregated price levels (caller must free) */
LxPriceLevel* lx_aggregated_orderbook_aggregated_bids(const LxAggregatedOrderbook* agg, size_t* count);
LxPriceLevel* lx_aggregated_orderbook_aggregated_asks(const LxAggregatedOrderbook* agg, size_t* count);
/* Find best venue for buying/selling amount */
bool lx_aggregated_orderbook_best_venue_buy(const LxAggregatedOrderbook* agg, LxDecimal amount,
char* venue, size_t venue_buf_size, LxDecimal* price);
bool lx_aggregated_orderbook_best_venue_sell(const LxAggregatedOrderbook* agg, LxDecimal amount,
char* venue, size_t venue_buf_size, LxDecimal* price);
#ifdef __cplusplus
}
#endif
#endif /* LX_TRADING_ORDERBOOK_H */
-225
View File
@@ -1,225 +0,0 @@
/**
* LX Trading SDK - Risk Management
* Thread-safe risk controls and position tracking
*/
#ifndef LX_TRADING_RISK_H
#define LX_TRADING_RISK_H
#include "types.h"
#include <pthread.h>
#include <stdatomic.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ============================================================================
* Risk Configuration
* ============================================================================ */
#define LX_RISK_MAX_POSITION_LIMITS 64
typedef struct {
char asset[LX_ASSET_MAX_LEN];
LxDecimal limit;
} LxPositionLimit;
typedef struct {
bool enabled;
LxDecimal max_position_size;
LxDecimal max_order_size;
LxDecimal max_daily_loss;
int max_open_orders;
bool kill_switch_enabled;
/* Per-asset position limits */
LxPositionLimit position_limits[LX_RISK_MAX_POSITION_LIMITS];
size_t position_limit_count;
} LxRiskConfig;
/* Initialize default config */
void lx_risk_config_init(LxRiskConfig* config);
/* Add a position limit for a specific asset */
int lx_risk_config_add_position_limit(LxRiskConfig* config, const char* asset, LxDecimal limit);
/* ============================================================================
* Risk Error Codes
* ============================================================================ */
typedef enum {
LX_RISK_OK = 0,
LX_RISK_ERROR_KILLED = -1,
LX_RISK_ERROR_ORDER_SIZE = -2,
LX_RISK_ERROR_POSITION_LIMIT = -3,
LX_RISK_ERROR_DAILY_LOSS = -4,
LX_RISK_ERROR_OPEN_ORDERS = -5,
LX_RISK_ERROR_DISABLED = -6
} LxRiskError;
/* Get error message for risk error code */
const char* lx_risk_error_message(LxRiskError error);
/* ============================================================================
* Position Entry
* ============================================================================ */
#define LX_RISK_MAX_POSITIONS 256
typedef struct {
char asset[LX_ASSET_MAX_LEN];
LxDecimal position;
} LxPositionEntry;
/* ============================================================================
* Open Orders Entry
* ============================================================================ */
#define LX_RISK_MAX_SYMBOLS 256
typedef struct {
char symbol[LX_SYMBOL_MAX_LEN * 2 + 2];
int count;
} LxOpenOrdersEntry;
/* ============================================================================
* Risk Manager
* ============================================================================
* Thread-safe risk manager with position tracking, PnL monitoring, and kill switch.
*/
typedef struct {
LxRiskConfig config;
atomic_bool kill_switch;
/* Position tracking */
LxPositionEntry positions[LX_RISK_MAX_POSITIONS];
size_t position_count;
pthread_rwlock_t position_lock;
/* PnL tracking */
LxDecimal daily_pnl;
pthread_rwlock_t pnl_lock;
/* Open orders tracking */
LxOpenOrdersEntry open_orders[LX_RISK_MAX_SYMBOLS];
size_t open_orders_count;
pthread_rwlock_t orders_lock;
} LxRiskManager;
/* ============================================================================
* Risk Manager Lifecycle
* ============================================================================ */
/* Initialize risk manager (returns 0 on success) */
int lx_risk_manager_init(LxRiskManager* rm, const LxRiskConfig* config);
/* Cleanup risk manager */
void lx_risk_manager_cleanup(LxRiskManager* rm);
/* Create/destroy on heap */
LxRiskManager* lx_risk_manager_create(const LxRiskConfig* config);
void lx_risk_manager_destroy(LxRiskManager* rm);
/* ============================================================================
* Risk Manager Configuration
* ============================================================================ */
bool lx_risk_manager_is_enabled(const LxRiskManager* rm);
const LxRiskConfig* lx_risk_manager_config(const LxRiskManager* rm);
/* ============================================================================
* Kill Switch
* ============================================================================ */
bool lx_risk_manager_is_killed(const LxRiskManager* rm);
void lx_risk_manager_kill(LxRiskManager* rm);
void lx_risk_manager_reset(LxRiskManager* rm);
/* ============================================================================
* Order Validation
* ============================================================================
* Returns LX_RISK_OK if valid, or error code if invalid.
*/
LxRiskError lx_risk_manager_validate_order(LxRiskManager* rm, const LxOrderRequest* request);
/* ============================================================================
* Position Tracking
* ============================================================================ */
/* Update position for an asset (adds delta based on side) */
void lx_risk_manager_update_position(LxRiskManager* rm, const char* asset,
LxDecimal quantity, LxSide side);
/* Get current position for an asset */
LxDecimal lx_risk_manager_position(const LxRiskManager* rm, const char* asset);
/* Get all positions (caller provides buffer) */
size_t lx_risk_manager_positions(const LxRiskManager* rm,
LxPositionEntry* entries, size_t max_entries);
/* ============================================================================
* PnL Tracking
* ============================================================================ */
/* Update daily PnL (accumulates) */
void lx_risk_manager_update_pnl(LxRiskManager* rm, LxDecimal pnl);
/* Get current daily PnL */
LxDecimal lx_risk_manager_daily_pnl(const LxRiskManager* rm);
/* Reset daily PnL to zero */
void lx_risk_manager_reset_daily_pnl(LxRiskManager* rm);
/* ============================================================================
* Order Tracking
* ============================================================================ */
/* Increment open orders for a symbol */
void lx_risk_manager_order_opened(LxRiskManager* rm, const char* symbol);
/* Decrement open orders for a symbol */
void lx_risk_manager_order_closed(LxRiskManager* rm, const char* symbol);
/* Get open order count for a symbol */
int lx_risk_manager_open_orders(const LxRiskManager* rm, const char* symbol);
/* ============================================================================
* Pre-Trade Checks (return bool, don't throw)
* ============================================================================ */
bool lx_risk_manager_check_order_size(const LxRiskManager* rm, LxDecimal quantity);
bool lx_risk_manager_check_position_limit(const LxRiskManager* rm, const char* asset,
LxDecimal new_position);
bool lx_risk_manager_check_daily_loss(const LxRiskManager* rm);
bool lx_risk_manager_check_open_orders(const LxRiskManager* rm, const char* symbol);
/* ============================================================================
* Order Tracker (RAII-style helper)
* ============================================================================
* Use lx_order_tracker_begin() to increment open orders, then
* lx_order_tracker_end() to decrement (unless released).
*/
typedef struct {
LxRiskManager* risk_manager;
char symbol[LX_SYMBOL_MAX_LEN * 2 + 2];
bool released;
} LxOrderTracker;
/* Begin tracking (increments open orders) */
void lx_order_tracker_begin(LxOrderTracker* tracker, LxRiskManager* rm, const char* symbol);
/* End tracking (decrements open orders unless released) */
void lx_order_tracker_end(LxOrderTracker* tracker);
/* Release tracker (prevents decrement on end) */
void lx_order_tracker_release(LxOrderTracker* tracker);
#ifdef __cplusplus
}
#endif
#endif /* LX_TRADING_RISK_H */
-345
View File
@@ -1,345 +0,0 @@
/**
* LX Trading SDK - Core Types
* Fixed-point decimal, trading structs, and enums for HFT
*/
#ifndef LX_TRADING_TYPES_H
#define LX_TRADING_TYPES_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ============================================================================
* Fixed-Point Decimal
* ============================================================================
* Stores value as integer * 10^(-8) for exact financial arithmetic.
*/
#define LX_DECIMAL_PRECISION 8
#define LX_DECIMAL_SCALE 100000000LL
typedef struct {
int64_t value;
} LxDecimal;
/* Decimal constructors */
LxDecimal lx_decimal_zero(void);
LxDecimal lx_decimal_one(void);
LxDecimal lx_decimal_from_int(int64_t value);
LxDecimal lx_decimal_from_double(double value);
LxDecimal lx_decimal_from_string(const char* str);
LxDecimal lx_decimal_from_scaled(int64_t scaled_value);
/* Decimal conversions */
double lx_decimal_to_double(LxDecimal d);
int lx_decimal_to_string(LxDecimal d, char* buf, size_t buf_size);
int64_t lx_decimal_scaled_value(LxDecimal d);
/* Decimal arithmetic */
LxDecimal lx_decimal_add(LxDecimal a, LxDecimal b);
LxDecimal lx_decimal_sub(LxDecimal a, LxDecimal b);
LxDecimal lx_decimal_mul(LxDecimal a, LxDecimal b);
LxDecimal lx_decimal_div(LxDecimal a, LxDecimal b);
LxDecimal lx_decimal_abs(LxDecimal d);
LxDecimal lx_decimal_neg(LxDecimal d);
/* Decimal comparisons */
bool lx_decimal_eq(LxDecimal a, LxDecimal b);
bool lx_decimal_ne(LxDecimal a, LxDecimal b);
bool lx_decimal_lt(LxDecimal a, LxDecimal b);
bool lx_decimal_le(LxDecimal a, LxDecimal b);
bool lx_decimal_gt(LxDecimal a, LxDecimal b);
bool lx_decimal_ge(LxDecimal a, LxDecimal b);
/* Decimal predicates */
bool lx_decimal_is_zero(LxDecimal d);
bool lx_decimal_is_positive(LxDecimal d);
bool lx_decimal_is_negative(LxDecimal d);
/* ============================================================================
* Trading Enums
* ============================================================================ */
typedef enum {
LX_SIDE_BUY = 0,
LX_SIDE_SELL = 1
} LxSide;
typedef enum {
LX_ORDER_TYPE_MARKET = 0,
LX_ORDER_TYPE_LIMIT = 1,
LX_ORDER_TYPE_LIMIT_MAKER = 2,
LX_ORDER_TYPE_STOP_LOSS = 3,
LX_ORDER_TYPE_STOP_LOSS_LIMIT = 4,
LX_ORDER_TYPE_TAKE_PROFIT = 5,
LX_ORDER_TYPE_TAKE_PROFIT_LIMIT = 6
} LxOrderType;
typedef enum {
LX_TIF_GTC = 0, /* Good till cancelled */
LX_TIF_IOC = 1, /* Immediate or cancel */
LX_TIF_FOK = 2, /* Fill or kill */
LX_TIF_GTD = 3, /* Good till date */
LX_TIF_POST_ONLY = 4 /* Post only */
} LxTimeInForce;
typedef enum {
LX_ORDER_STATUS_PENDING = 0,
LX_ORDER_STATUS_OPEN = 1,
LX_ORDER_STATUS_PARTIALLY_FILLED = 2,
LX_ORDER_STATUS_FILLED = 3,
LX_ORDER_STATUS_CANCELLED = 4,
LX_ORDER_STATUS_REJECTED = 5,
LX_ORDER_STATUS_EXPIRED = 6
} LxOrderStatus;
typedef enum {
LX_VENUE_NATIVE = 0,
LX_VENUE_CCXT = 1,
LX_VENUE_HUMMINGBOT = 2,
LX_VENUE_CUSTOM = 3
} LxVenueType;
/* Enum to string conversions */
const char* lx_side_to_string(LxSide side);
const char* lx_order_type_to_string(LxOrderType type);
const char* lx_tif_to_string(LxTimeInForce tif);
const char* lx_order_status_to_string(LxOrderStatus status);
/* ============================================================================
* Trading Pair
* ============================================================================ */
#define LX_SYMBOL_MAX_LEN 16
typedef struct {
char base[LX_SYMBOL_MAX_LEN];
char quote[LX_SYMBOL_MAX_LEN];
} LxTradingPair;
/* Parse symbol string (e.g., "BTC-USDC", "ETH/USD", "LUX_USDT") */
bool lx_trading_pair_from_symbol(const char* symbol, LxTradingPair* pair);
/* Format to different conventions */
int lx_trading_pair_to_hummingbot(const LxTradingPair* pair, char* buf, size_t buf_size);
int lx_trading_pair_to_ccxt(const LxTradingPair* pair, char* buf, size_t buf_size);
/* ============================================================================
* Price Level
* ============================================================================ */
typedef struct {
LxDecimal price;
LxDecimal quantity;
} LxPriceLevel;
/* Calculate notional value (price * quantity) */
LxDecimal lx_price_level_value(const LxPriceLevel* level);
/* ============================================================================
* Fee
* ============================================================================ */
#define LX_ASSET_MAX_LEN 32
typedef struct {
char asset[LX_ASSET_MAX_LEN];
LxDecimal amount;
bool has_rate;
LxDecimal rate;
} LxFee;
/* ============================================================================
* Balance
* ============================================================================ */
#define LX_VENUE_MAX_LEN 64
typedef struct {
char asset[LX_ASSET_MAX_LEN];
char venue[LX_VENUE_MAX_LEN];
LxDecimal free;
LxDecimal locked;
} LxBalance;
/* Get total balance (free + locked) */
LxDecimal lx_balance_total(const LxBalance* balance);
/* ============================================================================
* Order Request
* ============================================================================ */
#define LX_ORDER_ID_MAX_LEN 64
#define LX_CLIENT_ORDER_ID_MAX_LEN 64
typedef struct {
char symbol[LX_SYMBOL_MAX_LEN * 2 + 2]; /* "BASE-QUOTE" */
LxSide side;
LxOrderType order_type;
LxDecimal quantity;
bool has_price;
LxDecimal price;
bool has_stop_price;
LxDecimal stop_price;
LxTimeInForce time_in_force;
bool reduce_only;
bool post_only;
bool has_venue;
char venue[LX_VENUE_MAX_LEN];
char client_order_id[LX_CLIENT_ORDER_ID_MAX_LEN];
} LxOrderRequest;
/* Initialize an order request */
void lx_order_request_init(LxOrderRequest* req);
/* Factory functions */
LxOrderRequest lx_order_request_market(const char* symbol, LxSide side, LxDecimal quantity);
LxOrderRequest lx_order_request_limit(const char* symbol, LxSide side, LxDecimal quantity, LxDecimal price);
/* Builder functions (mutate in place, return pointer for chaining) */
LxOrderRequest* lx_order_request_with_venue(LxOrderRequest* req, const char* venue);
LxOrderRequest* lx_order_request_with_post_only(LxOrderRequest* req);
LxOrderRequest* lx_order_request_with_client_id(LxOrderRequest* req, const char* client_id);
LxOrderRequest* lx_order_request_with_stop_price(LxOrderRequest* req, LxDecimal stop_price);
/* ============================================================================
* Order
* ============================================================================ */
typedef struct {
char order_id[LX_ORDER_ID_MAX_LEN];
char client_order_id[LX_CLIENT_ORDER_ID_MAX_LEN];
char symbol[LX_SYMBOL_MAX_LEN * 2 + 2];
char venue[LX_VENUE_MAX_LEN];
LxSide side;
LxOrderType order_type;
LxOrderStatus status;
LxDecimal quantity;
LxDecimal filled_quantity;
LxDecimal remaining_quantity;
bool has_price;
LxDecimal price;
bool has_average_price;
LxDecimal average_price;
int64_t created_at;
int64_t updated_at;
/* Fees stored separately (typically small count) */
LxFee* fees;
size_t fee_count;
} LxOrder;
/* Order status checks */
bool lx_order_is_open(const LxOrder* order);
bool lx_order_is_done(const LxOrder* order);
LxDecimal lx_order_fill_percent(const LxOrder* order);
/* Initialize/cleanup */
void lx_order_init(LxOrder* order);
void lx_order_cleanup(LxOrder* order);
/* ============================================================================
* Trade/Fill
* ============================================================================ */
typedef struct {
char trade_id[LX_ORDER_ID_MAX_LEN];
char order_id[LX_ORDER_ID_MAX_LEN];
char symbol[LX_SYMBOL_MAX_LEN * 2 + 2];
char venue[LX_VENUE_MAX_LEN];
LxSide side;
LxDecimal price;
LxDecimal quantity;
LxFee fee;
int64_t timestamp;
bool is_maker;
} LxTrade;
/* Calculate trade value (price * quantity) */
LxDecimal lx_trade_value(const LxTrade* trade);
/* ============================================================================
* Ticker
* ============================================================================ */
typedef struct {
char symbol[LX_SYMBOL_MAX_LEN * 2 + 2];
char venue[LX_VENUE_MAX_LEN];
bool has_bid;
LxDecimal bid;
bool has_ask;
LxDecimal ask;
bool has_last;
LxDecimal last;
bool has_volume_24h;
LxDecimal volume_24h;
bool has_high_24h;
LxDecimal high_24h;
bool has_low_24h;
LxDecimal low_24h;
bool has_change_24h;
LxDecimal change_24h;
int64_t timestamp;
} LxTicker;
/* Ticker calculations */
bool lx_ticker_mid_price(const LxTicker* ticker, LxDecimal* result);
bool lx_ticker_spread(const LxTicker* ticker, LxDecimal* result);
bool lx_ticker_spread_percent(const LxTicker* ticker, LxDecimal* result);
/* ============================================================================
* Pool Info (AMM)
* ============================================================================ */
#define LX_ADDRESS_MAX_LEN 64
typedef struct {
char address[LX_ADDRESS_MAX_LEN];
char base_token[LX_ASSET_MAX_LEN];
char quote_token[LX_ASSET_MAX_LEN];
LxDecimal base_reserve;
LxDecimal quote_reserve;
LxDecimal total_liquidity;
LxDecimal fee_rate;
bool has_apy;
LxDecimal apy;
} LxPoolInfo;
/* ============================================================================
* Swap Quote
* ============================================================================ */
typedef struct {
char base_token[LX_ASSET_MAX_LEN];
char quote_token[LX_ASSET_MAX_LEN];
LxDecimal input_amount;
LxDecimal output_amount;
LxDecimal price;
LxDecimal price_impact;
LxDecimal fee;
char** route;
size_t route_count;
int64_t expires_at;
} LxSwapQuote;
/* Cleanup swap quote (frees route) */
void lx_swap_quote_cleanup(LxSwapQuote* quote);
/* ============================================================================
* Timestamp Utilities
* ============================================================================ */
int64_t lx_now_ms(void);
int64_t lx_now_us(void);
int64_t lx_now_ns(void);
#ifdef __cplusplus
}
#endif
#endif /* LX_TRADING_TYPES_H */
-650
View File
@@ -1,650 +0,0 @@
/**
* LX Trading SDK - Financial Mathematics Implementation
*/
#include "lx_trading/math.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
/* ============================================================================
* Statistical Functions
* ============================================================================ */
double lx_norm_cdf(double x) {
/* Abramowitz & Stegun approximation */
const double a1 = 0.254829592;
const double a2 = -0.284496736;
const double a3 = 1.421413741;
const double a4 = -1.453152027;
const double a5 = 1.061405429;
const double p = 0.3275911;
int sign = (x < 0) ? -1 : 1;
x = fabs(x) / LX_MATH_SQRT_2;
double t = 1.0 / (1.0 + p * x);
double y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * exp(-x * x);
return 0.5 * (1.0 + sign * y);
}
double lx_norm_pdf(double x) {
return exp(-0.5 * x * x) / LX_MATH_SQRT_2PI;
}
/* ============================================================================
* Black-Scholes Options Pricing
* ============================================================================ */
double lx_black_scholes(double S, double K, double T, double r, double sigma, bool is_call) {
if (T <= 0) {
/* At expiry */
if (is_call) {
return (S > K) ? S - K : 0.0;
} else {
return (K > S) ? K - S : 0.0;
}
}
if (sigma <= 0 || S <= 0 || K <= 0) {
return 0.0;
}
double sqrt_T = sqrt(T);
double d1 = (log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * sqrt_T);
double d2 = d1 - sigma * sqrt_T;
double discount = exp(-r * T);
if (is_call) {
return S * lx_norm_cdf(d1) - K * discount * lx_norm_cdf(d2);
} else {
return K * discount * lx_norm_cdf(-d2) - S * lx_norm_cdf(-d1);
}
}
double lx_implied_volatility(double price, double S, double K, double T, double r,
bool is_call, double tol, int max_iter) {
if (price <= 0 || S <= 0 || K <= 0 || T <= 0) {
return -1.0;
}
/* Newton-Raphson starting point */
double sigma = 0.2; /* Initial guess */
for (int i = 0; i < max_iter; i++) {
double calc_price = lx_black_scholes(S, K, T, r, sigma, is_call);
double diff = calc_price - price;
if (fabs(diff) < tol) {
return sigma;
}
/* Calculate vega */
double sqrt_T = sqrt(T);
double d1 = (log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * sqrt_T);
double vega = S * lx_norm_pdf(d1) * sqrt_T;
if (vega < 1e-10) {
/* Vega too small, can't converge */
break;
}
sigma -= diff / vega;
/* Bound sigma */
if (sigma < 0.001) sigma = 0.001;
if (sigma > 10.0) sigma = 10.0;
}
return -1.0; /* Did not converge */
}
LxGreeks lx_greeks(double S, double K, double T, double r, double sigma, bool is_call) {
LxGreeks g = {0};
if (T <= 0 || sigma <= 0 || S <= 0 || K <= 0) {
return g;
}
double sqrt_T = sqrt(T);
double d1 = (log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * sqrt_T);
double d2 = d1 - sigma * sqrt_T;
double nd1 = lx_norm_cdf(d1);
double nd2 = lx_norm_cdf(d2);
double pdf_d1 = lx_norm_pdf(d1);
double discount = exp(-r * T);
/* Delta */
if (is_call) {
g.delta = nd1;
} else {
g.delta = nd1 - 1.0;
}
/* Gamma */
g.gamma = pdf_d1 / (S * sigma * sqrt_T);
/* Vega (per 1% change in volatility) */
g.vega = S * pdf_d1 * sqrt_T * 0.01;
/* Theta (daily) */
double theta_term1 = -S * pdf_d1 * sigma / (2.0 * sqrt_T);
if (is_call) {
double theta_term2 = -r * K * discount * nd2;
g.theta = (theta_term1 + theta_term2) / 365.0;
} else {
double theta_term2 = r * K * discount * lx_norm_cdf(-d2);
g.theta = (theta_term1 + theta_term2) / 365.0;
}
/* Rho (per 1% change in rate) */
if (is_call) {
g.rho = K * T * discount * nd2 * 0.01;
} else {
g.rho = -K * T * discount * lx_norm_cdf(-d2) * 0.01;
}
return g;
}
/* ============================================================================
* AMM Pricing - Constant Product
* ============================================================================ */
void lx_constant_product_price(double reserve_x, double reserve_y,
double amount_in, double fee_rate, bool is_x_to_y,
double* output_amount, double* effective_price) {
if (!output_amount || !effective_price) return;
if (reserve_x <= 0 || reserve_y <= 0 || amount_in <= 0) {
*output_amount = 0;
*effective_price = 0;
return;
}
double amount_in_with_fee = amount_in * (1.0 - fee_rate);
double input_reserve, output_reserve;
if (is_x_to_y) {
input_reserve = reserve_x;
output_reserve = reserve_y;
} else {
input_reserve = reserve_y;
output_reserve = reserve_x;
}
/* xy = k, so output = output_reserve - k / (input_reserve + amount_in) */
double k = input_reserve * output_reserve;
double new_input_reserve = input_reserve + amount_in_with_fee;
*output_amount = output_reserve - k / new_input_reserve;
if (*output_amount > 0) {
*effective_price = *output_amount / amount_in;
} else {
*effective_price = 0;
}
}
/* ============================================================================
* AMM Pricing - Concentrated Liquidity
* ============================================================================ */
void lx_concentrated_liquidity_price(double liquidity,
double sqrt_price_current,
double sqrt_price_lower,
double sqrt_price_upper,
double amount_in,
double fee_rate,
bool is_token0_in,
double* output_amount,
double* new_sqrt_price,
double* price_impact) {
if (!output_amount || !new_sqrt_price || !price_impact) return;
if (liquidity <= 0 || amount_in <= 0 || sqrt_price_current <= 0) {
*output_amount = 0;
*new_sqrt_price = sqrt_price_current;
*price_impact = 0;
return;
}
double amount_in_with_fee = amount_in * (1.0 - fee_rate);
double initial_price = sqrt_price_current * sqrt_price_current;
if (is_token0_in) {
/* Token0 in, price increases */
double delta_sqrt_price = amount_in_with_fee / liquidity;
double new_sp = sqrt_price_current + delta_sqrt_price;
/* Clamp to range */
if (new_sp > sqrt_price_upper) new_sp = sqrt_price_upper;
/* Calculate token1 output */
*output_amount = liquidity * (new_sp - sqrt_price_current);
*new_sqrt_price = new_sp;
} else {
/* Token1 in, price decreases */
double delta_1_sqrt_price = amount_in_with_fee / liquidity;
double new_sp = sqrt_price_current - delta_1_sqrt_price / sqrt_price_current;
/* Clamp to range */
if (new_sp < sqrt_price_lower) new_sp = sqrt_price_lower;
/* Calculate token0 output */
*output_amount = liquidity * (1.0 / new_sp - 1.0 / sqrt_price_current);
if (*output_amount < 0) *output_amount = -*output_amount;
*new_sqrt_price = new_sp;
}
/* Calculate price impact */
double final_price = (*new_sqrt_price) * (*new_sqrt_price);
if (initial_price > 0) {
*price_impact = fabs(final_price - initial_price) / initial_price * 100.0;
} else {
*price_impact = 0;
}
}
double lx_calculate_liquidity(double amount_x, double amount_y,
double sqrt_price_current,
double sqrt_price_lower,
double sqrt_price_upper) {
if (sqrt_price_current <= 0 || sqrt_price_lower <= 0 || sqrt_price_upper <= 0) {
return 0;
}
if (sqrt_price_lower >= sqrt_price_upper) {
return 0;
}
double liquidity_x = 0;
double liquidity_y = 0;
if (sqrt_price_current <= sqrt_price_lower) {
/* All token0 */
if (amount_x > 0) {
liquidity_x = amount_x * sqrt_price_lower * sqrt_price_upper /
(sqrt_price_upper - sqrt_price_lower);
}
return liquidity_x;
} else if (sqrt_price_current >= sqrt_price_upper) {
/* All token1 */
if (amount_y > 0) {
liquidity_y = amount_y / (sqrt_price_upper - sqrt_price_lower);
}
return liquidity_y;
} else {
/* Both tokens */
if (amount_x > 0) {
liquidity_x = amount_x * sqrt_price_current * sqrt_price_upper /
(sqrt_price_upper - sqrt_price_current);
}
if (amount_y > 0) {
liquidity_y = amount_y / (sqrt_price_current - sqrt_price_lower);
}
return (liquidity_x < liquidity_y) ? liquidity_x : liquidity_y;
}
}
/* ============================================================================
* Price Conversions
* ============================================================================ */
double lx_price_to_sqrt_price(double price) {
return sqrt(price);
}
double lx_sqrt_price_to_price(double sqrt_price) {
return sqrt_price * sqrt_price;
}
double lx_tick_to_sqrt_price(int tick) {
return pow(1.0001, tick / 2.0);
}
int lx_sqrt_price_to_tick(double sqrt_price, int tick_spacing) {
if (sqrt_price <= 0 || tick_spacing <= 0) return 0;
int tick = (int)(2.0 * log(sqrt_price) / log(1.0001));
return (tick / tick_spacing) * tick_spacing;
}
/* ============================================================================
* Basic Statistics
* ============================================================================ */
double lx_sum(const double* data, size_t count) {
if (!data || count == 0) return 0;
double sum = 0;
for (size_t i = 0; i < count; i++) {
sum += data[i];
}
return sum;
}
double lx_mean(const double* data, size_t count) {
if (!data || count == 0) return 0;
return lx_sum(data, count) / (double)count;
}
double lx_variance(const double* data, size_t count) {
if (!data || count < 2) return 0;
double m = lx_mean(data, count);
double sum_sq = 0;
for (size_t i = 0; i < count; i++) {
double diff = data[i] - m;
sum_sq += diff * diff;
}
return sum_sq / (double)(count - 1); /* Sample variance */
}
double lx_std_dev(const double* data, size_t count) {
return sqrt(lx_variance(data, count));
}
/* ============================================================================
* Risk Metrics
* ============================================================================ */
double lx_volatility(const double* returns, size_t count,
bool annualize, int periods_per_year) {
if (!returns || count < 2) return 0;
double vol = lx_std_dev(returns, count);
if (annualize && periods_per_year > 0) {
vol *= sqrt((double)periods_per_year);
}
return vol;
}
double lx_sharpe_ratio(const double* returns, size_t count,
double risk_free_rate, int periods_per_year) {
if (!returns || count < 2) return 0;
double mean_return = lx_mean(returns, count);
double vol = lx_std_dev(returns, count);
if (vol < 1e-10) return 0;
double excess_return = mean_return - risk_free_rate / (double)periods_per_year;
double sharpe = excess_return / vol;
/* Annualize */
if (periods_per_year > 0) {
sharpe *= sqrt((double)periods_per_year);
}
return sharpe;
}
double lx_sortino_ratio(const double* returns, size_t count,
double risk_free_rate, double target_return,
int periods_per_year) {
if (!returns || count < 2) return 0;
double mean_return = lx_mean(returns, count);
/* Calculate downside deviation */
double sum_sq = 0;
size_t downside_count = 0;
for (size_t i = 0; i < count; i++) {
if (returns[i] < target_return) {
double diff = returns[i] - target_return;
sum_sq += diff * diff;
downside_count++;
}
}
if (downside_count == 0) {
return INFINITY; /* No downside */
}
double downside_dev = sqrt(sum_sq / (double)downside_count);
if (downside_dev < 1e-10) return 0;
double excess_return = mean_return - risk_free_rate / (double)periods_per_year;
double sortino = excess_return / downside_dev;
/* Annualize */
if (periods_per_year > 0) {
sortino *= sqrt((double)periods_per_year);
}
return sortino;
}
double lx_max_drawdown(const double* prices, size_t count,
size_t* peak_idx, size_t* trough_idx) {
if (!prices || count < 2) {
if (peak_idx) *peak_idx = 0;
if (trough_idx) *trough_idx = 0;
return 0;
}
double max_dd = 0;
double peak = prices[0];
size_t peak_i = 0;
size_t best_peak_i = 0;
size_t best_trough_i = 0;
for (size_t i = 1; i < count; i++) {
if (prices[i] > peak) {
peak = prices[i];
peak_i = i;
} else {
double dd = (peak - prices[i]) / peak;
if (dd > max_dd) {
max_dd = dd;
best_peak_i = peak_i;
best_trough_i = i;
}
}
}
if (peak_idx) *peak_idx = best_peak_i;
if (trough_idx) *trough_idx = best_trough_i;
return max_dd;
}
static int compare_doubles(const void* a, const void* b) {
double da = *(const double*)a;
double db = *(const double*)b;
if (da < db) return -1;
if (da > db) return 1;
return 0;
}
double lx_var(const double* returns, size_t count,
double confidence, bool parametric) {
if (!returns || count < 2 || confidence <= 0 || confidence >= 1) return 0;
if (parametric) {
/* Parametric VaR using normal distribution */
double m = lx_mean(returns, count);
double s = lx_std_dev(returns, count);
/* Z-score for confidence level (one-tailed) */
/* For 95%, z ~ 1.645; for 99%, z ~ 2.326 */
/* Using inverse of norm_cdf approximation */
double z;
if (confidence >= 0.99) {
z = 2.326;
} else if (confidence >= 0.95) {
z = 1.645;
} else {
/* Rough approximation */
z = sqrt(2.0) * (confidence - 0.5) * 3.0;
}
return -(m - z * s);
} else {
/* Historical VaR */
double* sorted = malloc(count * sizeof(double));
if (!sorted) return 0;
memcpy(sorted, returns, count * sizeof(double));
qsort(sorted, count, sizeof(double), compare_doubles);
size_t idx = (size_t)((1.0 - confidence) * (double)count);
if (idx >= count) idx = count - 1;
double var_val = -sorted[idx];
free(sorted);
return var_val;
}
}
double lx_cvar(const double* returns, size_t count, double confidence) {
if (!returns || count < 2 || confidence <= 0 || confidence >= 1) return 0;
double* sorted = malloc(count * sizeof(double));
if (!sorted) return 0;
memcpy(sorted, returns, count * sizeof(double));
qsort(sorted, count, sizeof(double), compare_doubles);
size_t cutoff = (size_t)((1.0 - confidence) * (double)count);
if (cutoff == 0) cutoff = 1;
if (cutoff > count) cutoff = count;
double sum = 0;
for (size_t i = 0; i < cutoff; i++) {
sum += sorted[i];
}
double cvar_val = -sum / (double)cutoff;
free(sorted);
return cvar_val;
}
/* ============================================================================
* Statistical Utilities
* ============================================================================ */
size_t lx_calculate_returns(const double* prices, size_t price_count, double* returns) {
if (!prices || !returns || price_count < 2) return 0;
for (size_t i = 1; i < price_count; i++) {
if (prices[i - 1] != 0) {
returns[i - 1] = (prices[i] - prices[i - 1]) / prices[i - 1];
} else {
returns[i - 1] = 0;
}
}
return price_count - 1;
}
size_t lx_rolling_mean(const double* data, size_t count, size_t window, double* result) {
if (!data || !result || count < window || window == 0) return 0;
size_t result_count = count - window + 1;
for (size_t i = 0; i < result_count; i++) {
double sum = 0;
for (size_t j = 0; j < window; j++) {
sum += data[i + j];
}
result[i] = sum / (double)window;
}
return result_count;
}
size_t lx_rolling_std(const double* data, size_t count, size_t window, double* result) {
if (!data || !result || count < window || window < 2) return 0;
size_t result_count = count - window + 1;
for (size_t i = 0; i < result_count; i++) {
/* Calculate mean */
double sum = 0;
for (size_t j = 0; j < window; j++) {
sum += data[i + j];
}
double mean = sum / (double)window;
/* Calculate variance */
double sum_sq = 0;
for (size_t j = 0; j < window; j++) {
double diff = data[i + j] - mean;
sum_sq += diff * diff;
}
result[i] = sqrt(sum_sq / (double)(window - 1));
}
return result_count;
}
void lx_ema(const double* data, size_t count, double alpha, double* result) {
if (!data || !result || count == 0 || alpha <= 0 || alpha > 1) return;
result[0] = data[0];
for (size_t i = 1; i < count; i++) {
result[i] = alpha * data[i] + (1.0 - alpha) * result[i - 1];
}
}
double lx_correlation(const double* x, const double* y, size_t count) {
if (!x || !y || count < 2) return 0;
double mean_x = lx_mean(x, count);
double mean_y = lx_mean(y, count);
double cov = 0;
double var_x = 0;
double var_y = 0;
for (size_t i = 0; i < count; i++) {
double dx = x[i] - mean_x;
double dy = y[i] - mean_y;
cov += dx * dy;
var_x += dx * dx;
var_y += dy * dy;
}
double denom = sqrt(var_x * var_y);
if (denom < 1e-10) return 0;
return cov / denom;
}
double lx_covariance(const double* x, const double* y, size_t count) {
if (!x || !y || count < 2) return 0;
double mean_x = lx_mean(x, count);
double mean_y = lx_mean(y, count);
double cov = 0;
for (size_t i = 0; i < count; i++) {
cov += (x[i] - mean_x) * (y[i] - mean_y);
}
return cov / (double)(count - 1);
}
double lx_beta(const double* asset_returns, const double* market_returns, size_t count) {
if (!asset_returns || !market_returns || count < 2) return 0;
double cov = lx_covariance(asset_returns, market_returns, count);
double var_market = lx_variance(market_returns, count);
if (var_market < 1e-10) return 0;
return cov / var_market;
}
File diff suppressed because it is too large Load Diff
-525
View File
@@ -1,525 +0,0 @@
/**
* LX Trading SDK - Risk Manager Implementation
*/
#include "lx_trading/risk.h"
#include <stdlib.h>
#include <string.h>
/* ============================================================================
* Risk Configuration
* ============================================================================ */
void lx_risk_config_init(LxRiskConfig* config) {
if (!config) return;
memset(config, 0, sizeof(LxRiskConfig));
config->enabled = true;
config->max_open_orders = 100;
}
int lx_risk_config_add_position_limit(LxRiskConfig* config, const char* asset, LxDecimal limit) {
if (!config || !asset) return -1;
if (config->position_limit_count >= LX_RISK_MAX_POSITION_LIMITS) return -1;
strncpy(config->position_limits[config->position_limit_count].asset,
asset, LX_ASSET_MAX_LEN - 1);
config->position_limits[config->position_limit_count].limit = limit;
config->position_limit_count++;
return 0;
}
/* ============================================================================
* Risk Error Messages
* ============================================================================ */
const char* lx_risk_error_message(LxRiskError error) {
switch (error) {
case LX_RISK_OK: return "OK";
case LX_RISK_ERROR_KILLED: return "Kill switch activated";
case LX_RISK_ERROR_ORDER_SIZE: return "Order size exceeds limit";
case LX_RISK_ERROR_POSITION_LIMIT: return "Position limit exceeded";
case LX_RISK_ERROR_DAILY_LOSS: return "Daily loss limit exceeded";
case LX_RISK_ERROR_OPEN_ORDERS: return "Maximum open orders exceeded";
case LX_RISK_ERROR_DISABLED: return "Risk management disabled";
default: return "Unknown error";
}
}
/* ============================================================================
* Risk Manager Lifecycle
* ============================================================================ */
int lx_risk_manager_init(LxRiskManager* rm, const LxRiskConfig* config) {
if (!rm) return -1;
memset(rm, 0, sizeof(LxRiskManager));
if (config) {
rm->config = *config;
} else {
lx_risk_config_init(&rm->config);
}
atomic_store(&rm->kill_switch, false);
rm->daily_pnl = lx_decimal_zero();
if (pthread_rwlock_init(&rm->position_lock, NULL) != 0) {
return -1;
}
if (pthread_rwlock_init(&rm->pnl_lock, NULL) != 0) {
pthread_rwlock_destroy(&rm->position_lock);
return -1;
}
if (pthread_rwlock_init(&rm->orders_lock, NULL) != 0) {
pthread_rwlock_destroy(&rm->position_lock);
pthread_rwlock_destroy(&rm->pnl_lock);
return -1;
}
return 0;
}
void lx_risk_manager_cleanup(LxRiskManager* rm) {
if (!rm) return;
pthread_rwlock_destroy(&rm->position_lock);
pthread_rwlock_destroy(&rm->pnl_lock);
pthread_rwlock_destroy(&rm->orders_lock);
}
LxRiskManager* lx_risk_manager_create(const LxRiskConfig* config) {
LxRiskManager* rm = malloc(sizeof(LxRiskManager));
if (!rm) return NULL;
if (lx_risk_manager_init(rm, config) != 0) {
free(rm);
return NULL;
}
return rm;
}
void lx_risk_manager_destroy(LxRiskManager* rm) {
if (!rm) return;
lx_risk_manager_cleanup(rm);
free(rm);
}
/* ============================================================================
* Risk Manager Configuration
* ============================================================================ */
bool lx_risk_manager_is_enabled(const LxRiskManager* rm) {
return rm ? rm->config.enabled : false;
}
const LxRiskConfig* lx_risk_manager_config(const LxRiskManager* rm) {
return rm ? &rm->config : NULL;
}
/* ============================================================================
* Kill Switch
* ============================================================================ */
bool lx_risk_manager_is_killed(const LxRiskManager* rm) {
return rm ? atomic_load(&rm->kill_switch) : false;
}
void lx_risk_manager_kill(LxRiskManager* rm) {
if (rm) atomic_store(&rm->kill_switch, true);
}
void lx_risk_manager_reset(LxRiskManager* rm) {
if (rm) atomic_store(&rm->kill_switch, false);
}
/* ============================================================================
* Helper: Extract asset from symbol
* ============================================================================ */
static void extract_base_asset(const char* symbol, char* asset, size_t asset_size) {
if (!symbol || !asset || asset_size == 0) return;
const char* sep = strchr(symbol, '-');
if (!sep) sep = strchr(symbol, '/');
if (!sep) sep = strchr(symbol, '_');
if (sep) {
size_t len = sep - symbol;
if (len >= asset_size) len = asset_size - 1;
strncpy(asset, symbol, len);
asset[len] = '\0';
} else {
strncpy(asset, symbol, asset_size - 1);
asset[asset_size - 1] = '\0';
}
}
/* ============================================================================
* Order Validation
* ============================================================================ */
LxRiskError lx_risk_manager_validate_order(LxRiskManager* rm, const LxOrderRequest* request) {
if (!rm || !request) return LX_RISK_ERROR_DISABLED;
/* Check if disabled */
if (!rm->config.enabled) {
return LX_RISK_OK;
}
/* Check kill switch */
if (atomic_load(&rm->kill_switch)) {
return LX_RISK_ERROR_KILLED;
}
/* Check order size */
if (!lx_decimal_is_zero(rm->config.max_order_size)) {
if (lx_decimal_gt(request->quantity, rm->config.max_order_size)) {
return LX_RISK_ERROR_ORDER_SIZE;
}
}
/* Check position limits */
char asset[LX_ASSET_MAX_LEN];
extract_base_asset(request->symbol, asset, sizeof(asset));
if (!lx_decimal_is_zero(rm->config.max_position_size)) {
pthread_rwlock_rdlock(&rm->position_lock);
LxDecimal current_pos = lx_decimal_zero();
for (size_t i = 0; i < rm->position_count; i++) {
if (strcmp(rm->positions[i].asset, asset) == 0) {
current_pos = rm->positions[i].position;
break;
}
}
pthread_rwlock_unlock(&rm->position_lock);
/* Calculate new position */
LxDecimal new_pos;
if (request->side == LX_SIDE_BUY) {
new_pos = lx_decimal_add(current_pos, request->quantity);
} else {
new_pos = lx_decimal_sub(current_pos, request->quantity);
}
new_pos = lx_decimal_abs(new_pos);
/* Check global limit */
if (lx_decimal_gt(new_pos, rm->config.max_position_size)) {
return LX_RISK_ERROR_POSITION_LIMIT;
}
/* Check asset-specific limit */
for (size_t i = 0; i < rm->config.position_limit_count; i++) {
if (strcmp(rm->config.position_limits[i].asset, asset) == 0) {
if (lx_decimal_gt(new_pos, rm->config.position_limits[i].limit)) {
return LX_RISK_ERROR_POSITION_LIMIT;
}
break;
}
}
}
/* Check daily loss */
if (!lx_decimal_is_zero(rm->config.max_daily_loss)) {
pthread_rwlock_rdlock(&rm->pnl_lock);
LxDecimal daily_pnl = rm->daily_pnl;
pthread_rwlock_unlock(&rm->pnl_lock);
if (lx_decimal_is_negative(daily_pnl)) {
LxDecimal loss = lx_decimal_neg(daily_pnl);
if (lx_decimal_gt(loss, rm->config.max_daily_loss)) {
return LX_RISK_ERROR_DAILY_LOSS;
}
}
}
/* Check open orders */
if (rm->config.max_open_orders > 0) {
pthread_rwlock_rdlock(&rm->orders_lock);
int open_count = 0;
for (size_t i = 0; i < rm->open_orders_count; i++) {
if (strcmp(rm->open_orders[i].symbol, request->symbol) == 0) {
open_count = rm->open_orders[i].count;
break;
}
}
pthread_rwlock_unlock(&rm->orders_lock);
if (open_count >= rm->config.max_open_orders) {
return LX_RISK_ERROR_OPEN_ORDERS;
}
}
return LX_RISK_OK;
}
/* ============================================================================
* Position Tracking
* ============================================================================ */
void lx_risk_manager_update_position(LxRiskManager* rm, const char* asset,
LxDecimal quantity, LxSide side) {
if (!rm || !asset) return;
pthread_rwlock_wrlock(&rm->position_lock);
/* Find existing position */
size_t idx = rm->position_count;
for (size_t i = 0; i < rm->position_count; i++) {
if (strcmp(rm->positions[i].asset, asset) == 0) {
idx = i;
break;
}
}
/* Create new position entry if needed */
if (idx == rm->position_count) {
if (rm->position_count < LX_RISK_MAX_POSITIONS) {
strncpy(rm->positions[idx].asset, asset, LX_ASSET_MAX_LEN - 1);
rm->positions[idx].position = lx_decimal_zero();
rm->position_count++;
} else {
pthread_rwlock_unlock(&rm->position_lock);
return;
}
}
/* Update position */
if (side == LX_SIDE_BUY) {
rm->positions[idx].position = lx_decimal_add(rm->positions[idx].position, quantity);
} else {
rm->positions[idx].position = lx_decimal_sub(rm->positions[idx].position, quantity);
}
pthread_rwlock_unlock(&rm->position_lock);
}
LxDecimal lx_risk_manager_position(const LxRiskManager* rm, const char* asset) {
if (!rm || !asset) return lx_decimal_zero();
pthread_rwlock_rdlock((pthread_rwlock_t*)&rm->position_lock);
LxDecimal pos = lx_decimal_zero();
for (size_t i = 0; i < rm->position_count; i++) {
if (strcmp(rm->positions[i].asset, asset) == 0) {
pos = rm->positions[i].position;
break;
}
}
pthread_rwlock_unlock((pthread_rwlock_t*)&rm->position_lock);
return pos;
}
size_t lx_risk_manager_positions(const LxRiskManager* rm,
LxPositionEntry* entries, size_t max_entries) {
if (!rm || !entries || max_entries == 0) return 0;
pthread_rwlock_rdlock((pthread_rwlock_t*)&rm->position_lock);
size_t count = rm->position_count < max_entries ? rm->position_count : max_entries;
memcpy(entries, rm->positions, count * sizeof(LxPositionEntry));
pthread_rwlock_unlock((pthread_rwlock_t*)&rm->position_lock);
return count;
}
/* ============================================================================
* PnL Tracking
* ============================================================================ */
void lx_risk_manager_update_pnl(LxRiskManager* rm, LxDecimal pnl) {
if (!rm) return;
pthread_rwlock_wrlock(&rm->pnl_lock);
rm->daily_pnl = lx_decimal_add(rm->daily_pnl, pnl);
pthread_rwlock_unlock(&rm->pnl_lock);
/* Check for kill switch trigger */
if (rm->config.kill_switch_enabled && !lx_decimal_is_zero(rm->config.max_daily_loss)) {
pthread_rwlock_rdlock(&rm->pnl_lock);
LxDecimal daily_pnl = rm->daily_pnl;
pthread_rwlock_unlock(&rm->pnl_lock);
if (lx_decimal_is_negative(daily_pnl)) {
LxDecimal loss = lx_decimal_neg(daily_pnl);
if (lx_decimal_gt(loss, rm->config.max_daily_loss)) {
atomic_store(&rm->kill_switch, true);
}
}
}
}
LxDecimal lx_risk_manager_daily_pnl(const LxRiskManager* rm) {
if (!rm) return lx_decimal_zero();
pthread_rwlock_rdlock((pthread_rwlock_t*)&rm->pnl_lock);
LxDecimal pnl = rm->daily_pnl;
pthread_rwlock_unlock((pthread_rwlock_t*)&rm->pnl_lock);
return pnl;
}
void lx_risk_manager_reset_daily_pnl(LxRiskManager* rm) {
if (!rm) return;
pthread_rwlock_wrlock(&rm->pnl_lock);
rm->daily_pnl = lx_decimal_zero();
pthread_rwlock_unlock(&rm->pnl_lock);
}
/* ============================================================================
* Order Tracking
* ============================================================================ */
void lx_risk_manager_order_opened(LxRiskManager* rm, const char* symbol) {
if (!rm || !symbol) return;
pthread_rwlock_wrlock(&rm->orders_lock);
/* Find existing entry */
for (size_t i = 0; i < rm->open_orders_count; i++) {
if (strcmp(rm->open_orders[i].symbol, symbol) == 0) {
rm->open_orders[i].count++;
pthread_rwlock_unlock(&rm->orders_lock);
return;
}
}
/* Create new entry */
if (rm->open_orders_count < LX_RISK_MAX_SYMBOLS) {
strncpy(rm->open_orders[rm->open_orders_count].symbol, symbol,
sizeof(rm->open_orders[rm->open_orders_count].symbol) - 1);
rm->open_orders[rm->open_orders_count].count = 1;
rm->open_orders_count++;
}
pthread_rwlock_unlock(&rm->orders_lock);
}
void lx_risk_manager_order_closed(LxRiskManager* rm, const char* symbol) {
if (!rm || !symbol) return;
pthread_rwlock_wrlock(&rm->orders_lock);
for (size_t i = 0; i < rm->open_orders_count; i++) {
if (strcmp(rm->open_orders[i].symbol, symbol) == 0) {
if (rm->open_orders[i].count > 0) {
rm->open_orders[i].count--;
}
break;
}
}
pthread_rwlock_unlock(&rm->orders_lock);
}
int lx_risk_manager_open_orders(const LxRiskManager* rm, const char* symbol) {
if (!rm || !symbol) return 0;
pthread_rwlock_rdlock((pthread_rwlock_t*)&rm->orders_lock);
int count = 0;
for (size_t i = 0; i < rm->open_orders_count; i++) {
if (strcmp(rm->open_orders[i].symbol, symbol) == 0) {
count = rm->open_orders[i].count;
break;
}
}
pthread_rwlock_unlock((pthread_rwlock_t*)&rm->orders_lock);
return count;
}
/* ============================================================================
* Pre-Trade Checks
* ============================================================================ */
bool lx_risk_manager_check_order_size(const LxRiskManager* rm, LxDecimal quantity) {
if (!rm || !rm->config.enabled) return true;
if (lx_decimal_is_zero(rm->config.max_order_size)) return true;
return lx_decimal_le(quantity, rm->config.max_order_size);
}
bool lx_risk_manager_check_position_limit(const LxRiskManager* rm, const char* asset,
LxDecimal new_position) {
if (!rm || !rm->config.enabled || !asset) return true;
if (lx_decimal_is_zero(rm->config.max_position_size)) return true;
LxDecimal abs_pos = lx_decimal_abs(new_position);
/* Check global limit */
if (lx_decimal_gt(abs_pos, rm->config.max_position_size)) {
return false;
}
/* Check asset-specific limit */
for (size_t i = 0; i < rm->config.position_limit_count; i++) {
if (strcmp(rm->config.position_limits[i].asset, asset) == 0) {
if (lx_decimal_gt(abs_pos, rm->config.position_limits[i].limit)) {
return false;
}
break;
}
}
return true;
}
bool lx_risk_manager_check_daily_loss(const LxRiskManager* rm) {
if (!rm || !rm->config.enabled) return true;
if (lx_decimal_is_zero(rm->config.max_daily_loss)) return true;
pthread_rwlock_rdlock((pthread_rwlock_t*)&rm->pnl_lock);
LxDecimal daily_pnl = rm->daily_pnl;
pthread_rwlock_unlock((pthread_rwlock_t*)&rm->pnl_lock);
if (lx_decimal_is_negative(daily_pnl)) {
LxDecimal loss = lx_decimal_neg(daily_pnl);
if (lx_decimal_gt(loss, rm->config.max_daily_loss)) {
return false;
}
}
return true;
}
bool lx_risk_manager_check_open_orders(const LxRiskManager* rm, const char* symbol) {
if (!rm || !rm->config.enabled || !symbol) return true;
if (rm->config.max_open_orders <= 0) return true;
int count = lx_risk_manager_open_orders(rm, symbol);
return count < rm->config.max_open_orders;
}
/* ============================================================================
* Order Tracker
* ============================================================================ */
void lx_order_tracker_begin(LxOrderTracker* tracker, LxRiskManager* rm, const char* symbol) {
if (!tracker || !rm || !symbol) return;
tracker->risk_manager = rm;
strncpy(tracker->symbol, symbol, sizeof(tracker->symbol) - 1);
tracker->symbol[sizeof(tracker->symbol) - 1] = '\0';
tracker->released = false;
lx_risk_manager_order_opened(rm, symbol);
}
void lx_order_tracker_end(LxOrderTracker* tracker) {
if (!tracker || tracker->released) return;
lx_risk_manager_order_closed(tracker->risk_manager, tracker->symbol);
}
void lx_order_tracker_release(LxOrderTracker* tracker) {
if (tracker) tracker->released = true;
}
-514
View File
@@ -1,514 +0,0 @@
/**
* LX Trading SDK - Types Implementation
*/
#include "lx_trading/types.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#include <time.h>
#endif
/* ============================================================================
* Decimal Implementation
* ============================================================================ */
LxDecimal lx_decimal_zero(void) {
LxDecimal d = {0};
return d;
}
LxDecimal lx_decimal_one(void) {
LxDecimal d = {LX_DECIMAL_SCALE};
return d;
}
LxDecimal lx_decimal_from_int(int64_t value) {
LxDecimal d = {value * LX_DECIMAL_SCALE};
return d;
}
LxDecimal lx_decimal_from_double(double value) {
LxDecimal d = {(int64_t)(value * LX_DECIMAL_SCALE)};
return d;
}
LxDecimal lx_decimal_from_string(const char* str) {
if (!str) return lx_decimal_zero();
int negative = 0;
const char* p = str;
/* Skip whitespace */
while (*p && isspace((unsigned char)*p)) p++;
/* Handle sign */
if (*p == '-') {
negative = 1;
p++;
} else if (*p == '+') {
p++;
}
int64_t integer_part = 0;
int64_t decimal_part = 0;
int decimal_places = 0;
/* Parse integer part */
while (*p && isdigit((unsigned char)*p)) {
integer_part = integer_part * 10 + (*p - '0');
p++;
}
/* Parse decimal part */
if (*p == '.') {
p++;
while (*p && isdigit((unsigned char)*p) && decimal_places < LX_DECIMAL_PRECISION) {
decimal_part = decimal_part * 10 + (*p - '0');
decimal_places++;
p++;
}
}
/* Scale decimal part */
int64_t scale_factor = LX_DECIMAL_SCALE;
for (int i = 0; i < decimal_places; i++) {
scale_factor /= 10;
}
int64_t value = integer_part * LX_DECIMAL_SCALE + decimal_part * scale_factor;
if (negative) value = -value;
LxDecimal d = {value};
return d;
}
LxDecimal lx_decimal_from_scaled(int64_t scaled_value) {
LxDecimal d = {scaled_value};
return d;
}
double lx_decimal_to_double(LxDecimal d) {
return (double)d.value / LX_DECIMAL_SCALE;
}
int lx_decimal_to_string(LxDecimal d, char* buf, size_t buf_size) {
if (!buf || buf_size == 0) return -1;
int negative = d.value < 0;
int64_t abs_value = negative ? -d.value : d.value;
int64_t integer_part = abs_value / LX_DECIMAL_SCALE;
int64_t decimal_part = abs_value % LX_DECIMAL_SCALE;
/* Remove trailing zeros */
int decimal_digits = LX_DECIMAL_PRECISION;
while (decimal_digits > 1 && decimal_part % 10 == 0) {
decimal_part /= 10;
decimal_digits--;
}
int written;
if (decimal_part == 0) {
written = snprintf(buf, buf_size, "%s%lld",
negative ? "-" : "",
(long long)integer_part);
} else {
written = snprintf(buf, buf_size, "%s%lld.%0*lld",
negative ? "-" : "",
(long long)integer_part,
decimal_digits,
(long long)decimal_part);
}
return written;
}
int64_t lx_decimal_scaled_value(LxDecimal d) {
return d.value;
}
LxDecimal lx_decimal_add(LxDecimal a, LxDecimal b) {
LxDecimal d = {a.value + b.value};
return d;
}
LxDecimal lx_decimal_sub(LxDecimal a, LxDecimal b) {
LxDecimal d = {a.value - b.value};
return d;
}
LxDecimal lx_decimal_mul(LxDecimal a, LxDecimal b) {
LxDecimal d = {(a.value * b.value) / LX_DECIMAL_SCALE};
return d;
}
LxDecimal lx_decimal_div(LxDecimal a, LxDecimal b) {
if (b.value == 0) {
LxDecimal d = {0};
return d;
}
LxDecimal d = {(a.value * LX_DECIMAL_SCALE) / b.value};
return d;
}
LxDecimal lx_decimal_abs(LxDecimal d) {
LxDecimal result = {d.value < 0 ? -d.value : d.value};
return result;
}
LxDecimal lx_decimal_neg(LxDecimal d) {
LxDecimal result = {-d.value};
return result;
}
bool lx_decimal_eq(LxDecimal a, LxDecimal b) { return a.value == b.value; }
bool lx_decimal_ne(LxDecimal a, LxDecimal b) { return a.value != b.value; }
bool lx_decimal_lt(LxDecimal a, LxDecimal b) { return a.value < b.value; }
bool lx_decimal_le(LxDecimal a, LxDecimal b) { return a.value <= b.value; }
bool lx_decimal_gt(LxDecimal a, LxDecimal b) { return a.value > b.value; }
bool lx_decimal_ge(LxDecimal a, LxDecimal b) { return a.value >= b.value; }
bool lx_decimal_is_zero(LxDecimal d) { return d.value == 0; }
bool lx_decimal_is_positive(LxDecimal d) { return d.value > 0; }
bool lx_decimal_is_negative(LxDecimal d) { return d.value < 0; }
/* ============================================================================
* Enum to String
* ============================================================================ */
const char* lx_side_to_string(LxSide side) {
return side == LX_SIDE_BUY ? "buy" : "sell";
}
const char* lx_order_type_to_string(LxOrderType type) {
switch (type) {
case LX_ORDER_TYPE_MARKET: return "market";
case LX_ORDER_TYPE_LIMIT: return "limit";
case LX_ORDER_TYPE_LIMIT_MAKER: return "limit_maker";
case LX_ORDER_TYPE_STOP_LOSS: return "stop_loss";
case LX_ORDER_TYPE_STOP_LOSS_LIMIT: return "stop_loss_limit";
case LX_ORDER_TYPE_TAKE_PROFIT: return "take_profit";
case LX_ORDER_TYPE_TAKE_PROFIT_LIMIT: return "take_profit_limit";
default: return "unknown";
}
}
const char* lx_tif_to_string(LxTimeInForce tif) {
switch (tif) {
case LX_TIF_GTC: return "GTC";
case LX_TIF_IOC: return "IOC";
case LX_TIF_FOK: return "FOK";
case LX_TIF_GTD: return "GTD";
case LX_TIF_POST_ONLY: return "POST_ONLY";
default: return "unknown";
}
}
const char* lx_order_status_to_string(LxOrderStatus status) {
switch (status) {
case LX_ORDER_STATUS_PENDING: return "pending";
case LX_ORDER_STATUS_OPEN: return "open";
case LX_ORDER_STATUS_PARTIALLY_FILLED: return "partially_filled";
case LX_ORDER_STATUS_FILLED: return "filled";
case LX_ORDER_STATUS_CANCELLED: return "cancelled";
case LX_ORDER_STATUS_REJECTED: return "rejected";
case LX_ORDER_STATUS_EXPIRED: return "expired";
default: return "unknown";
}
}
/* ============================================================================
* Trading Pair
* ============================================================================ */
bool lx_trading_pair_from_symbol(const char* symbol, LxTradingPair* pair) {
if (!symbol || !pair) return false;
memset(pair, 0, sizeof(LxTradingPair));
/* Find separator */
const char* sep = strchr(symbol, '-');
if (!sep) sep = strchr(symbol, '/');
if (!sep) sep = strchr(symbol, '_');
if (!sep) return false;
size_t base_len = sep - symbol;
size_t quote_len = strlen(sep + 1);
if (base_len >= LX_SYMBOL_MAX_LEN || quote_len >= LX_SYMBOL_MAX_LEN) {
return false;
}
strncpy(pair->base, symbol, base_len);
pair->base[base_len] = '\0';
strncpy(pair->quote, sep + 1, quote_len);
pair->quote[quote_len] = '\0';
return true;
}
int lx_trading_pair_to_hummingbot(const LxTradingPair* pair, char* buf, size_t buf_size) {
if (!pair || !buf || buf_size == 0) return -1;
return snprintf(buf, buf_size, "%s-%s", pair->base, pair->quote);
}
int lx_trading_pair_to_ccxt(const LxTradingPair* pair, char* buf, size_t buf_size) {
if (!pair || !buf || buf_size == 0) return -1;
return snprintf(buf, buf_size, "%s/%s", pair->base, pair->quote);
}
/* ============================================================================
* Price Level
* ============================================================================ */
LxDecimal lx_price_level_value(const LxPriceLevel* level) {
if (!level) return lx_decimal_zero();
return lx_decimal_mul(level->price, level->quantity);
}
/* ============================================================================
* Balance
* ============================================================================ */
LxDecimal lx_balance_total(const LxBalance* balance) {
if (!balance) return lx_decimal_zero();
return lx_decimal_add(balance->free, balance->locked);
}
/* ============================================================================
* Order Request
* ============================================================================ */
void lx_order_request_init(LxOrderRequest* req) {
if (!req) return;
memset(req, 0, sizeof(LxOrderRequest));
req->side = LX_SIDE_BUY;
req->order_type = LX_ORDER_TYPE_MARKET;
req->time_in_force = LX_TIF_GTC;
}
LxOrderRequest lx_order_request_market(const char* symbol, LxSide side, LxDecimal quantity) {
LxOrderRequest req;
lx_order_request_init(&req);
if (symbol) {
strncpy(req.symbol, symbol, sizeof(req.symbol) - 1);
}
req.side = side;
req.order_type = LX_ORDER_TYPE_MARKET;
req.quantity = quantity;
req.time_in_force = LX_TIF_IOC;
return req;
}
LxOrderRequest lx_order_request_limit(const char* symbol, LxSide side,
LxDecimal quantity, LxDecimal price) {
LxOrderRequest req;
lx_order_request_init(&req);
if (symbol) {
strncpy(req.symbol, symbol, sizeof(req.symbol) - 1);
}
req.side = side;
req.order_type = LX_ORDER_TYPE_LIMIT;
req.quantity = quantity;
req.has_price = true;
req.price = price;
req.time_in_force = LX_TIF_GTC;
return req;
}
LxOrderRequest* lx_order_request_with_venue(LxOrderRequest* req, const char* venue) {
if (!req) return NULL;
if (venue) {
req->has_venue = true;
strncpy(req->venue, venue, sizeof(req->venue) - 1);
}
return req;
}
LxOrderRequest* lx_order_request_with_post_only(LxOrderRequest* req) {
if (!req) return NULL;
req->post_only = true;
req->time_in_force = LX_TIF_POST_ONLY;
return req;
}
LxOrderRequest* lx_order_request_with_client_id(LxOrderRequest* req, const char* client_id) {
if (!req) return NULL;
if (client_id) {
strncpy(req->client_order_id, client_id, sizeof(req->client_order_id) - 1);
}
return req;
}
LxOrderRequest* lx_order_request_with_stop_price(LxOrderRequest* req, LxDecimal stop_price) {
if (!req) return NULL;
req->has_stop_price = true;
req->stop_price = stop_price;
return req;
}
/* ============================================================================
* Order
* ============================================================================ */
void lx_order_init(LxOrder* order) {
if (!order) return;
memset(order, 0, sizeof(LxOrder));
order->side = LX_SIDE_BUY;
order->order_type = LX_ORDER_TYPE_LIMIT;
order->status = LX_ORDER_STATUS_PENDING;
}
void lx_order_cleanup(LxOrder* order) {
if (!order) return;
if (order->fees) {
free(order->fees);
order->fees = NULL;
}
order->fee_count = 0;
}
bool lx_order_is_open(const LxOrder* order) {
if (!order) return false;
return order->status == LX_ORDER_STATUS_OPEN ||
order->status == LX_ORDER_STATUS_PARTIALLY_FILLED ||
order->status == LX_ORDER_STATUS_PENDING;
}
bool lx_order_is_done(const LxOrder* order) {
if (!order) return false;
return order->status == LX_ORDER_STATUS_FILLED ||
order->status == LX_ORDER_STATUS_CANCELLED ||
order->status == LX_ORDER_STATUS_REJECTED ||
order->status == LX_ORDER_STATUS_EXPIRED;
}
LxDecimal lx_order_fill_percent(const LxOrder* order) {
if (!order || lx_decimal_is_zero(order->quantity)) {
return lx_decimal_zero();
}
LxDecimal ratio = lx_decimal_div(order->filled_quantity, order->quantity);
return lx_decimal_mul(ratio, lx_decimal_from_double(100.0));
}
/* ============================================================================
* Trade
* ============================================================================ */
LxDecimal lx_trade_value(const LxTrade* trade) {
if (!trade) return lx_decimal_zero();
return lx_decimal_mul(trade->price, trade->quantity);
}
/* ============================================================================
* Ticker
* ============================================================================ */
bool lx_ticker_mid_price(const LxTicker* ticker, LxDecimal* result) {
if (!ticker || !result) return false;
if (!ticker->has_bid || !ticker->has_ask) {
if (ticker->has_last) {
*result = ticker->last;
return true;
}
return false;
}
*result = lx_decimal_div(lx_decimal_add(ticker->bid, ticker->ask),
lx_decimal_from_double(2.0));
return true;
}
bool lx_ticker_spread(const LxTicker* ticker, LxDecimal* result) {
if (!ticker || !result) return false;
if (!ticker->has_bid || !ticker->has_ask) return false;
*result = lx_decimal_sub(ticker->ask, ticker->bid);
return true;
}
bool lx_ticker_spread_percent(const LxTicker* ticker, LxDecimal* result) {
if (!ticker || !result) return false;
if (!ticker->has_bid || !ticker->has_ask) return false;
if (lx_decimal_is_zero(ticker->bid)) return false;
LxDecimal spread = lx_decimal_sub(ticker->ask, ticker->bid);
*result = lx_decimal_mul(lx_decimal_div(spread, ticker->bid),
lx_decimal_from_double(100.0));
return true;
}
/* ============================================================================
* Swap Quote
* ============================================================================ */
void lx_swap_quote_cleanup(LxSwapQuote* quote) {
if (!quote) return;
if (quote->route) {
for (size_t i = 0; i < quote->route_count; i++) {
free(quote->route[i]);
}
free(quote->route);
quote->route = NULL;
}
quote->route_count = 0;
}
/* ============================================================================
* Timestamp Utilities
* ============================================================================ */
int64_t lx_now_ms(void) {
#ifdef _WIN32
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
ULARGE_INTEGER ull;
ull.LowPart = ft.dwLowDateTime;
ull.HighPart = ft.dwHighDateTime;
return (int64_t)((ull.QuadPart - 116444736000000000ULL) / 10000);
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000;
#endif
}
int64_t lx_now_us(void) {
#ifdef _WIN32
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
ULARGE_INTEGER ull;
ull.LowPart = ft.dwLowDateTime;
ull.HighPart = ft.dwHighDateTime;
return (int64_t)((ull.QuadPart - 116444736000000000ULL) / 10);
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return (int64_t)tv.tv_sec * 1000000 + tv.tv_usec;
#endif
}
int64_t lx_now_ns(void) {
#ifdef _WIN32
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
ULARGE_INTEGER ull;
ull.LowPart = ft.dwLowDateTime;
ull.HighPart = ft.dwHighDateTime;
return (int64_t)((ull.QuadPart - 116444736000000000ULL) * 100);
#else
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return (int64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
#endif
}
-126
View File
@@ -1,126 +0,0 @@
cmake_minimum_required(VERSION 3.16)
project(lx_trading VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Allow older CMake policies for dependencies
set(CMAKE_POLICY_VERSION_MINIMUM 3.5)
# Options
option(LX_TRADING_BUILD_TESTS "Build tests" ON)
option(LX_TRADING_BUILD_EXAMPLES "Build examples" ON)
option(LX_TRADING_USE_SIMD "Enable SIMD optimizations" ON)
# Compiler flags
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
if(LX_TRADING_USE_SIMD)
add_compile_options(-march=native)
endif()
endif()
# Find dependencies
find_package(Threads REQUIRED)
# FetchContent for dependencies
include(FetchContent)
# nlohmann/json
FetchContent_Declare(
json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
)
# cpr (HTTP client)
FetchContent_Declare(
cpr
GIT_REPOSITORY https://github.com/libcpr/cpr.git
GIT_TAG 1.10.5
)
FetchContent_MakeAvailable(json cpr)
# Library
add_library(lx_trading
src/types.cpp
src/config.cpp
src/adapter.cpp
src/native_adapter.cpp
src/ccxt_adapter.cpp
src/hummingbot_adapter.cpp
src/orderbook.cpp
src/client.cpp
src/risk.cpp
src/execution.cpp
src/math.cpp
# Arbitrage module
src/arbitrage/types.cpp
src/arbitrage/scanner.cpp
src/arbitrage/lx_first.cpp
src/arbitrage/unified.cpp
src/arbitrage/cross_chain.cpp
)
target_include_directories(lx_trading
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(lx_trading
PUBLIC
nlohmann_json::nlohmann_json
cpr::cpr
Threads::Threads
)
# Tests
if(LX_TRADING_BUILD_TESTS)
enable_testing()
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.5.0
)
FetchContent_MakeAvailable(Catch2)
add_executable(lx_trading_tests
tests/test_types.cpp
tests/test_orderbook.cpp
tests/test_math.cpp
tests/test_risk.cpp
)
target_link_libraries(lx_trading_tests
PRIVATE
lx_trading
Catch2::Catch2WithMain
)
include(CTest)
include(Catch)
catch_discover_tests(lx_trading_tests)
endif()
# Examples
if(LX_TRADING_BUILD_EXAMPLES)
add_executable(example_basic examples/basic.cpp)
target_link_libraries(example_basic PRIVATE lx_trading)
add_executable(example_orderbook examples/orderbook_demo.cpp)
target_link_libraries(example_orderbook PRIVATE lx_trading)
endif()
# Install (simplified - dependencies handled separately)
include(GNUInstallDirs)
install(TARGETS lx_trading
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
-291
View File
@@ -1,291 +0,0 @@
# LX Trading SDK for C++
Modern C++17/20 high-frequency trading library for LX Exchange.
## Features
- **Zero-copy where possible**: Fixed-point decimal, inline string storage
- **Lock-free orderbook**: Thread-safe reads with minimal contention
- **SIMD optimizations**: AVX2/SSE4 for financial math when available
- **Smart order routing**: Aggregate orderbooks across multiple venues
- **Execution algorithms**: TWAP, VWAP, Iceberg, Sniper, POV
- **Risk management**: Thread-safe position and PnL tracking
- **Multi-venue support**: Native LX DEX/AMM, CCXT, Hummingbot Gateway
## Quick Start
```cpp
#include <lx/trading/client.hpp>
#include <lx/trading/config.hpp>
using namespace lx::trading;
int main() {
// Configure venues
Config config;
config.with_native("lx_dex",
NativeVenueConfig::lx_dex("https://api.lx.exchange")
.with_credentials("key", "secret"));
// Create client
Client client(config);
client.connect().get();
// Get aggregated orderbook
auto book = client.aggregated_orderbook("BTC-USDC").get();
// Place order with smart routing
auto order = client.buy("BTC-USDC", Decimal::from_double(0.1)).get();
client.disconnect().get();
}
```
## Building
Requirements:
- CMake 3.16+
- C++20 compiler (GCC 10+, Clang 12+, MSVC 2019+)
```bash
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . -j$(nproc)
# Run tests
ctest --output-on-failure
```
### CMake Options
| Option | Default | Description |
|--------|---------|-------------|
| `LX_TRADING_BUILD_TESTS` | ON | Build test suite |
| `LX_TRADING_BUILD_EXAMPLES` | ON | Build examples |
| `LX_TRADING_USE_SIMD` | ON | Enable SIMD optimizations |
## API Overview
### Configuration
```cpp
Config config;
// Native venues
config.with_native("lx_dex",
NativeVenueConfig::lx_dex("https://api.lx.exchange")
.with_credentials("key", "secret")
.with_wallet("0x...", "private_key"));
// CCXT exchanges (via REST proxy)
config.with_ccxt("binance",
CcxtConfig::create("binance")
.with_credentials("key", "secret")
.enable_sandbox());
// Hummingbot Gateway
config.with_hummingbot("uniswap",
HummingbotConfig::create("uniswap_lux_mainnet")
.with_wallet("0x...")
.with_endpoint("localhost", 15888));
// Risk management
config.set_max_order_size(Decimal::from_double(10.0))
.set_max_position_size(Decimal::from_double(100.0))
.set_max_daily_loss(Decimal::from_double(1000.0));
```
### Decimal Arithmetic
Fixed-point decimal with 8 decimal places for exact financial math:
```cpp
Decimal a = Decimal::from_double(100.5);
Decimal b = Decimal::from_string("50.25");
Decimal c = a + b; // 150.75
Decimal d = a * b; // 5050.125
std::string s = c.to_string(); // "150.75"
```
### Orderbook
```cpp
// Single venue
Orderbook book("BTC-USDC", "lx_dex");
book.add_bid(Decimal::from_double(50000), Decimal::from_double(1.5));
book.add_ask(Decimal::from_double(50010), Decimal::from_double(1.0));
book.sort();
auto vwap = book.vwap_buy(Decimal::from_double(2.0)); // VWAP for buying 2 BTC
// Aggregated across venues
AggregatedOrderbook agg("BTC-USDC");
agg.add_orderbook(book1);
agg.add_orderbook(book2);
auto best = agg.best_venue_buy(Decimal::from_double(1.0)); // Find best execution venue
```
### Order Placement
```cpp
// Market orders
auto order = client.buy("BTC-USDC", Decimal::from_double(0.1)).get();
auto order = client.sell("BTC-USDC", Decimal::from_double(0.1)).get();
// Limit orders
auto order = client.limit_buy("BTC-USDC",
Decimal::from_double(0.1),
Decimal::from_double(50000.0)).get();
// Full control
OrderRequest req = OrderRequest::limit("BTC-USDC", Side::Buy,
Decimal::from_double(0.1), Decimal::from_double(50000.0))
.with_venue("lx_dex")
.with_post_only()
.with_client_id("my-order-123");
auto order = client.place_order(req).get();
```
### AMM Operations
```cpp
// Get quote
auto quote = client.quote("ETH", "USDC",
Decimal::from_double(1.0), true, "lx_amm").get();
// Execute swap
auto trade = client.swap("ETH", "USDC",
Decimal::from_double(1.0), true, 0.01, "lx_amm").get();
// Liquidity management
auto result = client.add_liquidity("ETH", "USDC",
Decimal::from_double(1.0),
Decimal::from_double(2000.0),
0.01, "lx_amm").get();
```
### Execution Algorithms
```cpp
#include <lx/trading/execution.hpp>
// TWAP - spread order over time
auto twap = make_twap(client, "BTC-USDC", Side::Buy,
Decimal::from_double(10.0),
std::chrono::minutes(30),
10); // 10 slices
auto result = twap->execute().get();
// VWAP - participate in market volume
auto vwap = make_vwap(client, "BTC-USDC", Side::Buy,
Decimal::from_double(10.0),
Decimal::from_double(0.1), // 10% participation
std::chrono::hours(1));
// Iceberg - hidden large order
auto iceberg = make_iceberg(client, "BTC-USDC", Side::Buy,
Decimal::from_double(100.0), // total
Decimal::from_double(5.0), // visible
Decimal::from_double(50000.0));
// Sniper - wait for price then execute
auto sniper = make_sniper(client, "BTC-USDC", Side::Buy,
Decimal::from_double(1.0),
Decimal::from_double(49000.0), // target price
std::chrono::seconds(60));
```
### Risk Management
```cpp
auto& risk = client.risk_manager();
// Check before trading
if (!risk.check_order_size(qty)) {
// Order too large
}
// Manual control
risk.kill(); // Stop all trading
risk.reset(); // Resume trading
// Position tracking
risk.update_position("BTC", Decimal::from_double(1.0), Side::Buy);
Decimal pos = risk.position("BTC");
```
### Financial Math
```cpp
#include <lx/trading/math.hpp>
using namespace lx::trading::math;
// Options pricing
double call_price = black_scholes(100, 100, 1.0, 0.05, 0.2, true);
double iv = implied_volatility(10.45, 100, 100, 1.0, 0.05, true);
Greeks g = greeks(100, 100, 1.0, 0.05, 0.2, true);
// AMM math
auto [out, price] = constant_product_price(1000, 1000, 10, 0.003, true);
auto [out, new_sqrt_p, impact] = concentrated_liquidity_price(...);
// Risk metrics
double vol = volatility(returns, true, 252);
double sharpe = sharpe_ratio(returns, 0.05);
auto [dd, peak, trough] = max_drawdown(prices);
double var95 = var(returns, 0.95);
double cvar95 = cvar(returns, 0.95);
```
## Architecture
```
lx-trading-cpp/
├── include/lx/trading/
│ ├── types.hpp # Core types (Decimal, Side, Order, etc.)
│ ├── config.hpp # Configuration with builder pattern
│ ├── adapter.hpp # VenueAdapter interface
│ ├── adapters/
│ │ ├── native.hpp # LX DEX and AMM adapters
│ │ ├── ccxt.hpp # CCXT REST proxy adapter
│ │ └── hummingbot.hpp # Hummingbot Gateway adapter
│ ├── orderbook.hpp # Lock-free orderbook
│ ├── client.hpp # Unified trading client
│ ├── risk.hpp # Risk management
│ ├── execution.hpp # Execution algorithms
│ └── math.hpp # Financial mathematics
├── src/ # Implementation
├── tests/ # Catch2 tests
└── examples/ # Usage examples
```
## Thread Safety
- `Orderbook`: Thread-safe reads/writes via `std::shared_mutex`
- `RiskManager`: Thread-safe via atomic operations and locks
- `Client`: Future-based async API, safe for concurrent calls
- `Decimal`: Immutable, thread-safe by design
## Performance Considerations
1. **Fixed-point arithmetic**: `Decimal` uses int64 internally for exact math
2. **Inline storage**: `TradingPair` uses fixed arrays, no heap allocation
3. **Lock-free reads**: Orderbook allows concurrent readers
4. **SIMD**: Batch operations use AVX2 when available
5. **Zero-copy**: Futures return by value, move semantics throughout
## Dependencies
- [nlohmann/json](https://github.com/nlohmann/json) - JSON parsing
- [cpr](https://github.com/libcpr/cpr) - HTTP client
- [websocketpp](https://github.com/zaphoyd/websocketpp) - WebSocket (streaming)
- [Catch2](https://github.com/catchorg/Catch2) - Testing (optional)
All dependencies are fetched via CMake FetchContent.
## License
MIT
@@ -1,380 +0,0 @@
/**
* LX-First Arbitrage Bot Example
*
* This bot uses the LX-First strategy where LX DEX prices are treated
* as the "truth" (fastest venue with nanosecond updates, 200ms blocks).
* Other venues are always stale by comparison.
*
* Arbitrage = exploiting stale venues before they catch up to LX prices.
*
* Cross-chain transport options:
* - Warp: For Lux subnet communication (<500ms)
* - Teleport: For EVM chain bridging (~30s)
* - CEX API: Direct trading (instant)
*
* NO SMART CONTRACTS - just coordinated trades through unified SDK.
*/
#include <lx/trading/lx_dex.hpp>
#include <lx/trading/arbitrage/arbitrage.hpp>
#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>
#include <random>
#include <csignal>
#include <mutex>
using namespace lx::trading;
using namespace lx::trading::arbitrage;
// ============================================
// Global state
// ============================================
std::atomic<bool> g_running{false};
std::mutex g_stats_mutex;
struct Stats {
uint64_t total_opportunities = 0;
uint64_t total_executions = 0;
Decimal total_pnl{"0"};
};
Stats g_stats;
// ============================================
// Arbitrage Bot Class
// ============================================
class ArbitrageBot {
public:
ArbitrageBot() = default;
bool start() {
std::cout << std::string(60, '=') << std::endl;
std::cout << "LX-FIRST ARBITRAGE BOT" << std::endl;
std::cout << std::string(60, '=') << std::endl;
std::cout << std::endl;
// Initialize DEX client
LxDexConfig dex_config;
dex_config.endpoint = get_env("LX_DEX_ENDPOINT", "wss://dex.lux.network/ws");
dex_config.api_key = get_env("LX_API_KEY", "");
dex_ = std::make_shared<LxDex>(dex_config);
if (!dex_->connect()) {
std::cerr << "Failed to connect to LX DEX" << std::endl;
return false;
}
std::cout << "[OK] Connected to LX DEX" << std::endl;
// Initialize LX-First strategy
LxFirstConfig lx_config;
lx_config.max_staleness_ms = 2000;
lx_config.min_divergence_bps = Decimal("10");
lx_config.min_profit = Decimal("5");
lx_config.max_position_size = Decimal("10000");
lx_config.symbols = {"BTC-USDC", "ETH-USDC", "LUX-USDC"};
lx_config.venue_latencies = {
{"binance", 50},
{"mexc", 100},
{"okx", 80},
{"uniswap", 12000},
{"pancakeswap", 3000}
};
lx_first_ = std::make_shared<LxFirstArbitrage>(lx_config);
lx_first_->on_opportunity([this](const LxFirstOpportunity& opp) {
on_lx_first_opportunity(opp);
});
std::cout << "[OK] LX-First strategy initialized" << std::endl;
// Initialize Scanner
ScannerConfig scanner_config;
scanner_config.min_spread_bps = Decimal("10");
scanner_config.min_profit_usd = Decimal("10");
scanner_config.max_price_age_ms = 5000;
scanner_config.symbols = {"BTC", "ETH", "LUX", "SOL", "AVAX"};
scanner_config.chain_ids = {"lux", "ethereum", "bsc", "arbitrum", "polygon"};
scanner_config.scan_interval_ms = 100;
scanner_ = std::make_shared<Scanner>(scanner_config);
scanner_->on_opportunity([](const ArbitrageOpportunity& opp) {
std::cout << "[SCANNER] " << to_string(opp.type) << ": "
<< opp.buy_source.venue << " -> " << opp.sell_source.venue
<< " | Spread: " << opp.spread_bps.to_string() << " bps"
<< " | Net PnL: $" << opp.net_pnl.to_string()
<< std::endl;
});
std::cout << "[OK] Scanner initialized" << std::endl;
// Initialize Cross-Chain Router
router_ = std::make_shared<CrossChainRouter>(default_cross_chain_config());
std::cout << "[OK] Cross-chain router initialized" << std::endl;
// Start all systems
lx_first_->start();
scanner_->start();
g_running = true;
std::cout << std::endl;
std::cout << std::string(60, '=') << std::endl;
std::cout << "BOT RUNNING - Press Ctrl+C to stop" << std::endl;
std::cout << std::string(60, '=') << std::endl;
std::cout << std::endl;
std::cout << "Monitoring symbols: BTC-USDC, ETH-USDC, LUX-USDC" << std::endl;
std::cout << "Min divergence: 10 bps" << std::endl;
std::cout << "Min profit: $5" << std::endl;
std::cout << std::endl;
// Start price feed simulator thread
price_feed_thread_ = std::thread([this]() {
simulate_price_feeds();
});
// Start stats reporter thread
stats_thread_ = std::thread([this]() {
report_stats();
});
return true;
}
void stop() {
std::cout << "\nShutting down..." << std::endl;
g_running = false;
lx_first_->stop();
scanner_->stop();
if (price_feed_thread_.joinable()) {
price_feed_thread_.join();
}
if (stats_thread_.joinable()) {
stats_thread_.join();
}
print_final_stats();
}
private:
void on_lx_first_opportunity(const LxFirstOpportunity& opp) {
{
std::lock_guard<std::mutex> lock(g_stats_mutex);
g_stats.total_opportunities++;
}
std::cout << std::endl;
std::cout << std::string(50, '=') << std::endl;
std::cout << "LX-FIRST OPPORTUNITY DETECTED" << std::endl;
std::cout << std::string(50, '=') << std::endl;
std::cout << "Symbol: " << opp.symbol << std::endl;
std::cout << "LX Price: $" << opp.lx_price.mid.to_string() << std::endl;
std::cout << "Stale Venue: " << opp.stale_venue << std::endl;
std::cout << "Stale Bid/Ask: $" << opp.stale_price.bid.to_string()
<< " / $" << opp.stale_price.ask.to_string() << std::endl;
std::cout << "Staleness: " << opp.staleness << "ms" << std::endl;
std::cout << "Side: " << opp.side << std::endl;
std::cout << "Divergence: " << opp.divergence_bps.to_string() << " bps" << std::endl;
std::cout << "Expected Profit: $" << opp.expected_profit.to_string() << std::endl;
std::cout << "Confidence: " << (opp.confidence * 100) << "%" << std::endl;
std::cout << std::string(50, '=') << std::endl;
// Execute if confidence is high enough
if (opp.confidence > 0.8) {
execute_lx_first(opp);
}
}
void execute_lx_first(const LxFirstOpportunity& opp) {
std::cout << "\n[EXECUTING] " << opp.id << "..." << std::endl;
// Determine cross-chain transport
std::string buy_chain = router_->venue_to_chain(opp.stale_venue);
std::string sell_chain = "lux_mainnet";
auto transport = router_->determine_transport(buy_chain, sell_chain);
auto latency = router_->estimate_latency(buy_chain, sell_chain);
std::cout << " Transport: " << to_string(transport) << std::endl;
std::cout << " Est. Latency: " << latency << "ms" << std::endl;
if (opp.side == "buy") {
std::cout << " Buying on " << opp.stale_venue << "..." << std::endl;
// In production: place actual order
// auto order = cex_client->place_order(...)
std::cout << " Hedging on LX DEX..." << std::endl;
// In production: place hedge order
// auto hedge = dex_->spot()->sell(...)
} else {
std::cout << " Selling on " << opp.stale_venue << "..." << std::endl;
// In production: place actual order
std::cout << " Hedging on LX DEX..." << std::endl;
// In production: place hedge order
}
// Simulate successful execution
Decimal profit = opp.expected_profit * Decimal("0.8"); // Simulate slippage
{
std::lock_guard<std::mutex> lock(g_stats_mutex);
g_stats.total_executions++;
g_stats.total_pnl = g_stats.total_pnl + profit;
}
std::cout << "[SUCCESS] Executed " << opp.id
<< " | Profit: $" << profit.to_string() << std::endl;
}
void simulate_price_feeds() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> price_dist(-0.001, 0.001);
std::uniform_real_distribution<> divergence_dist(-0.002, 0.002);
std::uniform_int_distribution<> block_dist(1000000, 2000000);
struct BasePair {
std::string symbol;
Decimal base;
};
std::vector<BasePair> base_prices = {
{"BTC-USDC", Decimal("50000")},
{"ETH-USDC", Decimal("3000")},
{"LUX-USDC", Decimal("25")}
};
while (g_running) {
auto now = std::chrono::system_clock::now();
auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()).count();
for (const auto& pair : base_prices) {
// Simulate LX DEX price (the oracle)
double variance = price_dist(gen);
Decimal lx_mid = pair.base * Decimal(std::to_string(1.0 + variance));
LxPrice lx_price;
lx_price.symbol = pair.symbol;
lx_price.bid = lx_mid * Decimal("0.9999");
lx_price.ask = lx_mid * Decimal("1.0001");
lx_price.mid = lx_mid;
lx_price.timestamp = timestamp;
lx_price.block_num = block_dist(gen);
lx_first_->update_lx_price(lx_price);
// Simulate stale CEX prices
std::vector<std::pair<std::string, int>> venues = {
{"binance", 50},
{"mexc", 100}
};
for (const auto& [venue, latency] : venues) {
double divergence = divergence_dist(gen);
Decimal venue_mid = pair.base * Decimal(std::to_string(1.0 + divergence));
VenuePrice venue_price;
venue_price.venue = venue;
venue_price.symbol = pair.symbol;
venue_price.bid = venue_mid * Decimal("0.9998");
venue_price.ask = venue_mid * Decimal("1.0002");
venue_price.timestamp = timestamp - latency;
venue_price.latency = latency;
venue_price.stale = false;
lx_first_->update_venue_price(venue_price);
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void report_stats() {
while (g_running) {
std::this_thread::sleep_for(std::chrono::seconds(30));
if (!g_running) break;
std::lock_guard<std::mutex> lock(g_stats_mutex);
std::cout << std::endl;
std::cout << std::string(40, '-') << std::endl;
std::cout << "STATS" << std::endl;
std::cout << " Opportunities: " << g_stats.total_opportunities << std::endl;
std::cout << " Executions: " << g_stats.total_executions << std::endl;
std::cout << " Total PnL: $" << g_stats.total_pnl.to_string() << std::endl;
if (g_stats.total_executions > 0) {
Decimal avg_pnl = g_stats.total_pnl / Decimal(std::to_string(g_stats.total_executions));
std::cout << " Avg PnL: $" << avg_pnl.to_string() << std::endl;
}
std::cout << std::string(40, '-') << std::endl;
}
}
void print_final_stats() {
std::lock_guard<std::mutex> lock(g_stats_mutex);
std::cout << std::endl;
std::cout << std::string(50, '=') << std::endl;
std::cout << "FINAL STATISTICS" << std::endl;
std::cout << std::string(50, '=') << std::endl;
std::cout << "Total Opportunities: " << g_stats.total_opportunities << std::endl;
std::cout << "Total Executions: " << g_stats.total_executions << std::endl;
std::cout << "Total PnL: $" << g_stats.total_pnl.to_string() << std::endl;
if (g_stats.total_executions > 0 && g_stats.total_opportunities > 0) {
double win_rate = (static_cast<double>(g_stats.total_executions) /
static_cast<double>(g_stats.total_opportunities)) * 100.0;
Decimal avg_pnl = g_stats.total_pnl / Decimal(std::to_string(g_stats.total_executions));
std::cout << "Execution Rate: " << win_rate << "%" << std::endl;
std::cout << "Avg PnL per Trade: $" << avg_pnl.to_string() << std::endl;
}
std::cout << std::string(50, '=') << std::endl;
}
static std::string get_env(const std::string& key, const std::string& default_value) {
const char* value = std::getenv(key.c_str());
return value ? value : default_value;
}
std::shared_ptr<LxDex> dex_;
std::shared_ptr<LxFirstArbitrage> lx_first_;
std::shared_ptr<Scanner> scanner_;
std::shared_ptr<CrossChainRouter> router_;
std::thread price_feed_thread_;
std::thread stats_thread_;
};
// ============================================
// Main Entry Point
// ============================================
ArbitrageBot* g_bot = nullptr;
void signal_handler(int signal) {
if (g_bot) {
g_bot->stop();
}
std::exit(0);
}
int main() {
// Set up signal handlers
std::signal(SIGINT, signal_handler);
std::signal(SIGTERM, signal_handler);
ArbitrageBot bot;
g_bot = &bot;
if (!bot.start()) {
return 1;
}
// Keep running until signal
while (g_running) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}
-98
View File
@@ -1,98 +0,0 @@
// LX Trading SDK - Basic Example
// Demonstrates client setup, market data, and order placement
#include <lx/trading/client.hpp>
#include <lx/trading/config.hpp>
#include <iostream>
using namespace lx::trading;
int main() {
// Build configuration
Config config;
config.general.smart_routing = true;
config.general.timeout_ms = 30000;
// Add LX DEX
config.with_native("lx_dex",
NativeVenueConfig::lx_dex("https://api.lx.exchange")
.with_credentials("your-api-key", "your-api-secret"));
// Add LX AMM
config.with_native("lx_amm",
NativeVenueConfig::lx_amm("https://amm.lx.exchange"));
// Risk management
config.set_max_order_size(Decimal::from_double(10.0))
.set_max_position_size(Decimal::from_double(100.0))
.set_max_daily_loss(Decimal::from_double(1000.0));
// Create client
Client client(config);
try {
// Connect to all venues
std::cout << "Connecting to venues...\n";
client.connect().get();
// List connected venues
for (const auto& venue : client.venues()) {
std::cout << "Connected: " << venue.name
<< " (latency: " << venue.latency_ms.value_or(0) << "ms)\n";
}
// Get aggregated orderbook
std::cout << "\nFetching BTC-USDC orderbook...\n";
auto book = client.aggregated_orderbook("BTC-USDC").get();
if (auto best_bid = book.best_bid()) {
auto [price, venue, qty] = *best_bid;
std::cout << "Best bid: " << price.to_string()
<< " @ " << venue << "\n";
}
if (auto best_ask = book.best_ask()) {
auto [price, venue, qty] = *best_ask;
std::cout << "Best ask: " << price.to_string()
<< " @ " << venue << "\n";
}
// Get ticker
std::cout << "\nFetching ticker...\n";
auto ticker = client.ticker("BTC-USDC").get();
std::cout << "Mid price: " << ticker.mid_price().value_or(Decimal::zero()).to_string() << "\n";
std::cout << "Spread: " << ticker.spread().value_or(Decimal::zero()).to_string() << "\n";
// Get balances
std::cout << "\nFetching balances...\n";
auto balances = client.balances().get();
for (const auto& bal : balances) {
std::cout << bal.asset << ": " << bal.total().to_string() << "\n";
}
// Place a limit order
std::cout << "\nPlacing limit buy order...\n";
auto order = client.limit_buy(
"BTC-USDC",
Decimal::from_double(0.1),
Decimal::from_double(40000.0)).get();
std::cout << "Order placed: " << order.order_id << "\n";
std::cout << "Status: " << to_string(order.status) << "\n";
// Cancel the order
std::cout << "\nCancelling order...\n";
auto cancelled = client.cancel_order(order.order_id, order.symbol, order.venue).get();
std::cout << "Cancelled: " << to_string(cancelled.status) << "\n";
// Disconnect
client.disconnect().get();
std::cout << "\nDisconnected.\n";
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
return 1;
}
return 0;
}
@@ -1,191 +0,0 @@
// LX Trading SDK - Orderbook Demo
// Demonstrates lock-free orderbook and aggregation
#include <lx/trading/orderbook.hpp>
#include <lx/trading/math.hpp>
#include <iostream>
#include <thread>
#include <vector>
using namespace lx::trading;
using namespace lx::trading::math;
void print_orderbook(const Orderbook& book) {
std::cout << "\n=== " << book.symbol() << " @ " << book.venue() << " ===\n";
std::cout << "Timestamp: " << book.timestamp() << "\n";
auto bids = book.bids();
auto asks = book.asks();
std::cout << "\nAsks:\n";
for (auto it = asks.rbegin(); it != asks.rend(); ++it) {
std::cout << " " << it->price.to_string() << " x " << it->quantity.to_string() << "\n";
}
std::cout << "---\n";
std::cout << "Bids:\n";
for (const auto& bid : bids) {
std::cout << " " << bid.price.to_string() << " x " << bid.quantity.to_string() << "\n";
}
if (auto mid = book.mid_price()) {
std::cout << "\nMid: " << mid->to_string() << "\n";
}
if (auto spread = book.spread_percent()) {
std::cout << "Spread: " << spread->to_string() << "%\n";
}
}
void concurrent_update_demo() {
std::cout << "\n=== Concurrent Update Demo ===\n";
Orderbook book("BTC-USDC", "test");
// Multiple threads updating
std::vector<std::thread> writers;
for (int t = 0; t < 4; ++t) {
writers.emplace_back([&book, t]() {
for (int i = 0; i < 100; ++i) {
double price = 100.0 + t * 10 + (i % 10);
double qty = 1.0 + (i % 5) * 0.1;
if (t % 2 == 0) {
book.add_bid(Decimal::from_double(price), Decimal::from_double(qty));
} else {
book.add_ask(Decimal::from_double(price + 50), Decimal::from_double(qty));
}
}
});
}
// Reader thread
std::thread reader([&book]() {
for (int i = 0; i < 10; ++i) {
auto bids = book.bids();
auto asks = book.asks();
std::cout << "Read: " << bids.size() << " bids, " << asks.size() << " asks\n";
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
});
for (auto& w : writers) w.join();
reader.join();
book.sort();
std::cout << "Final: " << book.bids().size() << " bids, " << book.asks().size() << " asks\n";
}
void aggregation_demo() {
std::cout << "\n=== Aggregation Demo ===\n";
// Simulate orderbooks from multiple venues
Orderbook binance("BTC-USDC", "binance");
binance.add_bid(Decimal::from_double(50000.0), Decimal::from_double(1.5));
binance.add_bid(Decimal::from_double(49990.0), Decimal::from_double(2.0));
binance.add_ask(Decimal::from_double(50010.0), Decimal::from_double(1.0));
binance.add_ask(Decimal::from_double(50020.0), Decimal::from_double(1.5));
binance.sort();
Orderbook lx_dex("BTC-USDC", "lx_dex");
lx_dex.add_bid(Decimal::from_double(50005.0), Decimal::from_double(0.8)); // Best bid!
lx_dex.add_bid(Decimal::from_double(49995.0), Decimal::from_double(1.2));
lx_dex.add_ask(Decimal::from_double(50008.0), Decimal::from_double(0.5)); // Best ask!
lx_dex.add_ask(Decimal::from_double(50015.0), Decimal::from_double(1.0));
lx_dex.sort();
print_orderbook(binance);
print_orderbook(lx_dex);
// Aggregate
AggregatedOrderbook agg("BTC-USDC");
agg.add_orderbook(binance);
agg.add_orderbook(lx_dex);
std::cout << "\n=== Aggregated ===\n";
if (auto best = agg.best_bid()) {
auto [price, venue, qty] = *best;
std::cout << "Best bid: " << price.to_string() << " @ " << venue << "\n";
}
if (auto best = agg.best_ask()) {
auto [price, venue, qty] = *best;
std::cout << "Best ask: " << price.to_string() << " @ " << venue << "\n";
}
// Best venue for buying 1 BTC
if (auto best = agg.best_venue_buy(Decimal::from_double(1.0))) {
auto [venue, price] = *best;
std::cout << "Buy 1 BTC best at: " << venue << " @ " << price.to_string() << "\n";
}
// Best venue for selling 1 BTC
if (auto best = agg.best_venue_sell(Decimal::from_double(1.0))) {
auto [venue, price] = *best;
std::cout << "Sell 1 BTC best at: " << venue << " @ " << price.to_string() << "\n";
}
std::cout << "\nAggregated asks:\n";
for (const auto& level : agg.aggregated_asks()) {
std::cout << " " << level.price.to_string() << " x " << level.quantity.to_string() << "\n";
}
}
void vwap_demo() {
std::cout << "\n=== VWAP Demo ===\n";
Orderbook book("ETH-USDC", "test");
// Create realistic orderbook
book.add_ask(Decimal::from_double(2000.0), Decimal::from_double(5.0));
book.add_ask(Decimal::from_double(2001.0), Decimal::from_double(10.0));
book.add_ask(Decimal::from_double(2002.0), Decimal::from_double(15.0));
book.add_ask(Decimal::from_double(2005.0), Decimal::from_double(20.0));
book.add_ask(Decimal::from_double(2010.0), Decimal::from_double(50.0));
book.sort();
// Calculate VWAP for different sizes
std::cout << "VWAP for buying:\n";
for (double size : {1.0, 5.0, 10.0, 25.0, 50.0}) {
auto vwap = book.vwap_buy(Decimal::from_double(size));
if (vwap) {
std::cout << " " << size << " ETH: " << vwap->to_string() << "\n";
} else {
std::cout << " " << size << " ETH: insufficient liquidity\n";
}
}
}
void amm_math_demo() {
std::cout << "\n=== AMM Math Demo ===\n";
// Constant product (Uniswap V2)
double reserve_eth = 1000.0;
double reserve_usdc = 2000000.0; // Price = 2000
std::cout << "Constant Product Pool:\n";
std::cout << "ETH reserve: " << reserve_eth << "\n";
std::cout << "USDC reserve: " << reserve_usdc << "\n";
std::cout << "Implied price: " << (reserve_usdc / reserve_eth) << "\n\n";
for (double amt : {1.0, 10.0, 50.0, 100.0}) {
auto [out, price] = constant_product_price(reserve_eth, reserve_usdc, amt, 0.003, true);
double slippage = (2000.0 - price * 1000) / 2000.0 * 100;
std::cout << "Buy " << amt << " ETH -> " << out << " USDC"
<< " (price: " << (out / amt) << ", slippage: " << slippage << "%)\n";
}
}
int main() {
std::cout << "LX Trading SDK - Orderbook Demo\n";
std::cout << "================================\n";
concurrent_update_demo();
aggregation_demo();
vwap_demo();
amm_math_demo();
return 0;
}
@@ -1,205 +0,0 @@
// LX Trading SDK - Venue Adapter Interface
// Abstract interface for all trading venues
#pragma once
#include <lx/trading/types.hpp>
#include <functional>
#include <future>
#include <memory>
#include <set>
#include <string>
#include <vector>
namespace lx::trading {
// Forward declarations
class Orderbook;
// Venue capabilities
struct VenueCapabilities {
bool limit_orders = false;
bool market_orders = false;
bool stop_orders = false;
bool post_only = false;
bool cancel_orders = false;
bool batch_orders = false;
bool streaming = false;
bool orderbook = false;
bool trades = false;
bool amm_swap = false;
bool add_liquidity = false;
bool remove_liquidity = false;
bool lp_positions = false;
int max_batch_size = 1;
std::set<std::string> supported_pairs;
static VenueCapabilities clob() {
VenueCapabilities caps;
caps.limit_orders = true;
caps.market_orders = true;
caps.stop_orders = true;
caps.post_only = true;
caps.cancel_orders = true;
caps.batch_orders = true;
caps.streaming = true;
caps.orderbook = true;
caps.trades = true;
caps.max_batch_size = 10;
return caps;
}
static VenueCapabilities amm() {
VenueCapabilities caps;
caps.market_orders = true;
caps.streaming = true;
caps.trades = true;
caps.amm_swap = true;
caps.add_liquidity = true;
caps.remove_liquidity = true;
caps.lp_positions = true;
return caps;
}
};
// Adapter error
class AdapterError : public std::runtime_error {
public:
explicit AdapterError(const std::string& msg) : std::runtime_error(msg) {}
};
// Base adapter interface
class VenueAdapter {
public:
virtual ~VenueAdapter() = default;
// Properties
[[nodiscard]] virtual std::string_view name() const = 0;
[[nodiscard]] virtual VenueType venue_type() const = 0;
[[nodiscard]] virtual const VenueCapabilities& capabilities() const = 0;
[[nodiscard]] virtual bool is_connected() const = 0;
[[nodiscard]] virtual std::optional<int> latency_ms() const { return std::nullopt; }
// Venue info
[[nodiscard]] VenueInfo info() const {
VenueInfo vi;
vi.name = std::string(name());
vi.venue_type = venue_type();
vi.connected = is_connected();
vi.latency_ms = latency_ms();
const auto& caps = capabilities();
vi.supported_pairs = std::vector<std::string>(caps.supported_pairs.begin(),
caps.supported_pairs.end());
vi.maker_fee = Decimal::from_double(0.001);
vi.taker_fee = Decimal::from_double(0.002);
return vi;
}
// Connection
virtual std::future<void> connect() = 0;
virtual std::future<void> disconnect() = 0;
// Market data
virtual std::future<std::vector<MarketInfo>> get_markets() = 0;
virtual std::future<Ticker> get_ticker(const std::string& symbol) = 0;
virtual std::future<std::unique_ptr<Orderbook>> get_orderbook(
const std::string& symbol,
std::optional<int> depth = std::nullopt) = 0;
virtual std::future<std::vector<Trade>> get_trades(
const std::string& symbol,
std::optional<int> limit = std::nullopt) = 0;
// Account
virtual std::future<std::vector<Balance>> get_balances() = 0;
virtual std::future<Balance> get_balance(const std::string& asset) = 0;
virtual std::future<std::vector<Order>> get_open_orders(
const std::optional<std::string>& symbol = std::nullopt) = 0;
// Orders
virtual std::future<Order> place_order(const OrderRequest& request) = 0;
virtual std::future<Order> cancel_order(const std::string& order_id,
const std::string& symbol) = 0;
virtual std::future<std::vector<Order>> cancel_all_orders(
const std::optional<std::string>& symbol = std::nullopt) = 0;
// AMM operations (optional - throw AdapterError if not supported)
virtual std::future<SwapQuote> get_swap_quote(
const std::string& base_token,
const std::string& quote_token,
Decimal amount,
bool is_buy) {
return std::async(std::launch::deferred, []() -> SwapQuote {
throw AdapterError("AMM swap not supported");
});
}
virtual std::future<Trade> execute_swap(
const std::string& base_token,
const std::string& quote_token,
Decimal amount,
bool is_buy,
Decimal slippage) {
return std::async(std::launch::deferred, []() -> Trade {
throw AdapterError("AMM swap not supported");
});
}
virtual std::future<PoolInfo> get_pool_info(
const std::string& base_token,
const std::string& quote_token) {
return std::async(std::launch::deferred, []() -> PoolInfo {
throw AdapterError("Pool info not supported");
});
}
virtual std::future<LiquidityResult> add_liquidity(
const std::string& base_token,
const std::string& quote_token,
Decimal base_amount,
Decimal quote_amount,
Decimal slippage) {
return std::async(std::launch::deferred, []() -> LiquidityResult {
throw AdapterError("Add liquidity not supported");
});
}
virtual std::future<LiquidityResult> remove_liquidity(
const std::string& pool_address,
Decimal liquidity_amount,
Decimal slippage) {
return std::async(std::launch::deferred, []() -> LiquidityResult {
throw AdapterError("Remove liquidity not supported");
});
}
virtual std::future<std::vector<LpPosition>> get_lp_positions() {
return std::async(std::launch::deferred, []() -> std::vector<LpPosition> {
throw AdapterError("LP positions not supported");
});
}
// Streaming callbacks
using TickerCallback = std::function<void(const Ticker&)>;
using TradeCallback = std::function<void(const Trade&)>;
using OrderbookCallback = std::function<void(const Orderbook&)>;
using OrderCallback = std::function<void(const Order&)>;
virtual void subscribe_ticker(const std::string& symbol, TickerCallback cb) {
(void)symbol; (void)cb; // Default: no streaming
}
virtual void subscribe_trades(const std::string& symbol, TradeCallback cb) {
(void)symbol; (void)cb;
}
virtual void subscribe_orderbook(const std::string& symbol, OrderbookCallback cb) {
(void)symbol; (void)cb;
}
virtual void subscribe_orders(OrderCallback cb) {
(void)cb;
}
virtual void unsubscribe_all() {}
};
// Adapter factory type
using AdapterFactory = std::function<std::unique_ptr<VenueAdapter>()>;
} // namespace lx::trading
@@ -1,69 +0,0 @@
// LX Trading SDK - CCXT Adapter
// REST proxy to CCXT service for 100+ exchanges
#pragma once
#include <lx/trading/adapter.hpp>
#include <lx/trading/config.hpp>
#include <nlohmann/json_fwd.hpp>
#include <atomic>
#include <memory>
namespace lx::trading {
// CCXT Adapter - connects to a CCXT REST service
// This allows C++ to leverage CCXT's exchange coverage via a REST API
class CcxtAdapter : public VenueAdapter {
public:
CcxtAdapter(std::string_view name, const CcxtConfig& config);
~CcxtAdapter() override;
// VenueAdapter interface
[[nodiscard]] std::string_view name() const override { return name_; }
[[nodiscard]] VenueType venue_type() const override { return VenueType::Ccxt; }
[[nodiscard]] const VenueCapabilities& capabilities() const override { return capabilities_; }
[[nodiscard]] bool is_connected() const override { return connected_.load(); }
[[nodiscard]] std::optional<int> latency_ms() const override {
int lat = latency_.load();
return lat > 0 ? std::optional<int>(lat) : std::nullopt;
}
std::future<void> connect() override;
std::future<void> disconnect() override;
std::future<std::vector<MarketInfo>> get_markets() override;
std::future<Ticker> get_ticker(const std::string& symbol) override;
std::future<std::unique_ptr<Orderbook>> get_orderbook(
const std::string& symbol,
std::optional<int> depth = std::nullopt) override;
std::future<std::vector<Trade>> get_trades(
const std::string& symbol,
std::optional<int> limit = std::nullopt) override;
std::future<std::vector<Balance>> get_balances() override;
std::future<Balance> get_balance(const std::string& asset) override;
std::future<std::vector<Order>> get_open_orders(
const std::optional<std::string>& symbol = std::nullopt) override;
std::future<Order> place_order(const OrderRequest& request) override;
std::future<Order> cancel_order(const std::string& order_id,
const std::string& symbol) override;
std::future<std::vector<Order>> cancel_all_orders(
const std::optional<std::string>& symbol = std::nullopt) override;
// Set the CCXT service URL (default: http://localhost:3000)
void set_service_url(std::string_view url);
private:
Order convert_order(const nlohmann::json& json);
void update_latency(int64_t start_ns);
std::string name_;
CcxtConfig config_;
std::string service_url_ = "http://localhost:3000";
VenueCapabilities capabilities_;
std::atomic<bool> connected_{false};
std::atomic<int> latency_{0};
};
} // namespace lx::trading
@@ -1,97 +0,0 @@
// LX Trading SDK - Hummingbot Gateway Adapter
// Connect to Hummingbot Gateway for DEX trading
#pragma once
#include <lx/trading/adapter.hpp>
#include <lx/trading/config.hpp>
#include <nlohmann/json_fwd.hpp>
#include <atomic>
#include <memory>
namespace lx::trading {
// Hummingbot Gateway Adapter
// Connects to Hummingbot Gateway REST API for DEX operations
class HummingbotAdapter : public VenueAdapter {
public:
HummingbotAdapter(std::string_view name, const HummingbotConfig& config);
~HummingbotAdapter() override;
// VenueAdapter interface
[[nodiscard]] std::string_view name() const override { return name_; }
[[nodiscard]] VenueType venue_type() const override { return VenueType::Hummingbot; }
[[nodiscard]] const VenueCapabilities& capabilities() const override { return capabilities_; }
[[nodiscard]] bool is_connected() const override { return connected_.load(); }
[[nodiscard]] std::optional<int> latency_ms() const override {
int lat = latency_.load();
return lat > 0 ? std::optional<int>(lat) : std::nullopt;
}
std::future<void> connect() override;
std::future<void> disconnect() override;
std::future<std::vector<MarketInfo>> get_markets() override;
std::future<Ticker> get_ticker(const std::string& symbol) override;
std::future<std::unique_ptr<Orderbook>> get_orderbook(
const std::string& symbol,
std::optional<int> depth = std::nullopt) override;
std::future<std::vector<Trade>> get_trades(
const std::string& symbol,
std::optional<int> limit = std::nullopt) override;
std::future<std::vector<Balance>> get_balances() override;
std::future<Balance> get_balance(const std::string& asset) override;
std::future<std::vector<Order>> get_open_orders(
const std::optional<std::string>& symbol = std::nullopt) override;
std::future<Order> place_order(const OrderRequest& request) override;
std::future<Order> cancel_order(const std::string& order_id,
const std::string& symbol) override;
std::future<std::vector<Order>> cancel_all_orders(
const std::optional<std::string>& symbol = std::nullopt) override;
// AMM operations
std::future<SwapQuote> get_swap_quote(
const std::string& base_token,
const std::string& quote_token,
Decimal amount,
bool is_buy) override;
std::future<Trade> execute_swap(
const std::string& base_token,
const std::string& quote_token,
Decimal amount,
bool is_buy,
Decimal slippage) override;
std::future<PoolInfo> get_pool_info(
const std::string& base_token,
const std::string& quote_token) override;
std::future<LiquidityResult> add_liquidity(
const std::string& base_token,
const std::string& quote_token,
Decimal base_amount,
Decimal quote_amount,
Decimal slippage) override;
std::future<LiquidityResult> remove_liquidity(
const std::string& pool_address,
Decimal liquidity_amount,
Decimal slippage) override;
std::future<std::vector<LpPosition>> get_lp_positions() override;
private:
nlohmann::json build_request_body();
void update_latency(int64_t start_ns);
std::string name_;
HummingbotConfig config_;
VenueCapabilities capabilities_;
std::atomic<bool> connected_{false};
std::atomic<int> latency_{0};
};
} // namespace lx::trading
@@ -1,153 +0,0 @@
// LX Trading SDK - Native Adapters
// LX DEX (CLOB) and LX AMM adapters
#pragma once
#include <lx/trading/adapter.hpp>
#include <lx/trading/config.hpp>
#include <lx/trading/orderbook.hpp>
#include <nlohmann/json_fwd.hpp>
#include <atomic>
#include <memory>
#include <mutex>
#include <string>
namespace lx::trading {
// HTTP client abstraction (implemented with cpr)
class HttpClient;
// LX DEX Adapter - Central Limit Order Book
class LxDexAdapter : public VenueAdapter {
public:
LxDexAdapter(std::string_view name, const NativeVenueConfig& config);
~LxDexAdapter() override;
// VenueAdapter interface
[[nodiscard]] std::string_view name() const override { return name_; }
[[nodiscard]] VenueType venue_type() const override { return VenueType::Native; }
[[nodiscard]] const VenueCapabilities& capabilities() const override { return capabilities_; }
[[nodiscard]] bool is_connected() const override { return connected_.load(); }
[[nodiscard]] std::optional<int> latency_ms() const override {
int lat = latency_.load();
return lat > 0 ? std::optional<int>(lat) : std::nullopt;
}
std::future<void> connect() override;
std::future<void> disconnect() override;
std::future<std::vector<MarketInfo>> get_markets() override;
std::future<Ticker> get_ticker(const std::string& symbol) override;
std::future<std::unique_ptr<Orderbook>> get_orderbook(
const std::string& symbol,
std::optional<int> depth = std::nullopt) override;
std::future<std::vector<Trade>> get_trades(
const std::string& symbol,
std::optional<int> limit = std::nullopt) override;
std::future<std::vector<Balance>> get_balances() override;
std::future<Balance> get_balance(const std::string& asset) override;
std::future<std::vector<Order>> get_open_orders(
const std::optional<std::string>& symbol = std::nullopt) override;
std::future<Order> place_order(const OrderRequest& request) override;
std::future<Order> cancel_order(const std::string& order_id,
const std::string& symbol) override;
std::future<std::vector<Order>> cancel_all_orders(
const std::optional<std::string>& symbol = std::nullopt) override;
private:
Order convert_order(const nlohmann::json& json);
void update_latency(int64_t start_ns);
std::string name_;
NativeVenueConfig config_;
VenueCapabilities capabilities_;
std::unique_ptr<HttpClient> http_;
std::atomic<bool> connected_{false};
std::atomic<int> latency_{0};
};
// LX AMM Adapter - Automated Market Maker
class LxAmmAdapter : public VenueAdapter {
public:
LxAmmAdapter(std::string_view name, const NativeVenueConfig& config);
~LxAmmAdapter() override;
// VenueAdapter interface
[[nodiscard]] std::string_view name() const override { return name_; }
[[nodiscard]] VenueType venue_type() const override { return VenueType::Native; }
[[nodiscard]] const VenueCapabilities& capabilities() const override { return capabilities_; }
[[nodiscard]] bool is_connected() const override { return connected_.load(); }
[[nodiscard]] std::optional<int> latency_ms() const override {
int lat = latency_.load();
return lat > 0 ? std::optional<int>(lat) : std::nullopt;
}
std::future<void> connect() override;
std::future<void> disconnect() override;
std::future<std::vector<MarketInfo>> get_markets() override;
std::future<Ticker> get_ticker(const std::string& symbol) override;
std::future<std::unique_ptr<Orderbook>> get_orderbook(
const std::string& symbol,
std::optional<int> depth = std::nullopt) override;
std::future<std::vector<Trade>> get_trades(
const std::string& symbol,
std::optional<int> limit = std::nullopt) override;
std::future<std::vector<Balance>> get_balances() override;
std::future<Balance> get_balance(const std::string& asset) override;
std::future<std::vector<Order>> get_open_orders(
const std::optional<std::string>& symbol = std::nullopt) override;
std::future<Order> place_order(const OrderRequest& request) override;
std::future<Order> cancel_order(const std::string& order_id,
const std::string& symbol) override;
std::future<std::vector<Order>> cancel_all_orders(
const std::optional<std::string>& symbol = std::nullopt) override;
// AMM-specific operations
std::future<SwapQuote> get_swap_quote(
const std::string& base_token,
const std::string& quote_token,
Decimal amount,
bool is_buy) override;
std::future<Trade> execute_swap(
const std::string& base_token,
const std::string& quote_token,
Decimal amount,
bool is_buy,
Decimal slippage) override;
std::future<PoolInfo> get_pool_info(
const std::string& base_token,
const std::string& quote_token) override;
std::future<LiquidityResult> add_liquidity(
const std::string& base_token,
const std::string& quote_token,
Decimal base_amount,
Decimal quote_amount,
Decimal slippage) override;
std::future<LiquidityResult> remove_liquidity(
const std::string& pool_address,
Decimal liquidity_amount,
Decimal slippage) override;
std::future<std::vector<LpPosition>> get_lp_positions() override;
private:
void update_latency(int64_t start_ns);
std::string name_;
NativeVenueConfig config_;
VenueCapabilities capabilities_;
std::unique_ptr<HttpClient> http_;
std::atomic<bool> connected_{false};
std::atomic<int> latency_{0};
};
} // namespace lx::trading
@@ -1,24 +0,0 @@
// LX Trading SDK - Arbitrage Module
//
// LX-First Arbitrage Strategy:
// - LX DEX is the FASTEST venue (nanosecond price updates, 200ms blocks)
// - By the time other venues update, LX has already moved
// - LX DEX price is the "TRUTH" (most current)
// - Other venues are always STALE by comparison
// - Arbitrage = correcting stale venues to match LX
//
// Key Concepts:
// 1. LX as Oracle: LX DEX provides the reference price
// 2. Stale Venues: CEX and external DEX are behind by 50ms-12s
// 3. Front-running: Trade on stale venues before they catch up
// 4. Cross-chain: Warp for Lux subnets, Teleport for EVM
//
// NO SMART CONTRACTS - All arbitrage is executed through native RPC and the unified SDK.
#pragma once
#include <lx/trading/arbitrage/types.hpp>
#include <lx/trading/arbitrage/scanner.hpp>
#include <lx/trading/arbitrage/lx_first.hpp>
#include <lx/trading/arbitrage/unified.hpp>
#include <lx/trading/arbitrage/cross_chain.hpp>
@@ -1,121 +0,0 @@
// LX Trading SDK - Cross-Chain Arbitrage Transports
//
// 1. WARP (Lux Native)
// - Only works WITHIN Lux ecosystem (between subnets)
// - Sub-second message delivery (<500ms)
// - Use for: LX DEX <-> LX AMM <-> Other Lux subnets
// - Cannot reach external chains
//
// 2. TELEPORT (EVM Bridge)
// - Works with ANY EVM-compatible chain
// - Lux <-> Ethereum, BSC, Arbitrum, Polygon, etc.
// - ~30 second finality (depends on source chain)
// - Uses validator attestations
//
// 3. CEX API
// - No bridging needed - just API calls
// - Sub-second execution
// - Settlement via withdraw/deposit (slow but doesn't block arb)
//
// 4. FOR OMNICHAIN ARBITRAGE:
// - Lux internal: Warp (instant)
// - External EVM: Teleport (~30s)
// - CEX: Direct API (instant trade, later settle)
#pragma once
#include <lx/trading/arbitrage/types.hpp>
#include <memory>
#include <mutex>
namespace lx::trading::arbitrage {
/// Warp client interface for Lux-native messaging
class WarpClient {
public:
virtual ~WarpClient() = default;
/// Send a Warp message to another Lux subnet
virtual std::string send_message(const std::string& dest_subnet,
const std::vector<uint8_t>& payload) = 0;
/// Receive a Warp message
virtual std::vector<uint8_t> receive_message(const std::string& message_id) = 0;
/// Get this subnet's ID
virtual std::string get_blockchain_id() const = 0;
};
/// Teleport client interface for EVM bridging
class TeleportClient {
public:
virtual ~TeleportClient() = default;
/// Bridge assets to another EVM chain
virtual std::string bridge(const std::string& dest_chain,
const std::string& token,
Decimal amount) = 0;
/// Get bridge transaction status
virtual BridgeStatus get_bridge_status(const std::string& tx_id) = 0;
/// Estimate bridge fee
virtual Decimal estimate_bridge_fee(const std::string& dest_chain,
const std::string& token,
Decimal amount) = 0;
};
/// Cross-chain router for determining optimal transport
class CrossChainRouter {
public:
explicit CrossChainRouter(CrossChainConfig config);
~CrossChainRouter() = default;
// Non-copyable
CrossChainRouter(const CrossChainRouter&) = delete;
CrossChainRouter& operator=(const CrossChainRouter&) = delete;
/// Set the Warp client
void set_warp_client(std::shared_ptr<WarpClient> client);
/// Set the Teleport client
void set_teleport_client(std::shared_ptr<TeleportClient> client);
/// Get the Warp client
[[nodiscard]] std::shared_ptr<WarpClient> warp() const;
/// Get the Teleport client
[[nodiscard]] std::shared_ptr<TeleportClient> teleport() const;
/// Determine the best transport between two chains
[[nodiscard]] CrossChainTransport determine_transport(const std::string& source_chain,
const std::string& dest_chain) const;
/// Estimate latency for cross-chain message (ms)
[[nodiscard]] int64_t estimate_latency(const std::string& source_chain,
const std::string& dest_chain) const;
/// Estimate cost for cross-chain transfer
[[nodiscard]] Decimal estimate_cost(const std::string& source_chain,
const std::string& dest_chain,
const std::string& token,
Decimal amount) const;
/// Get chain ID from venue name
[[nodiscard]] std::string venue_to_chain(const std::string& venue) const;
/// Enhance an opportunity with routing information
[[nodiscard]] EnhancedOpportunity enhance_opportunity(const UnifiedOpportunity& opp) const;
/// Get current config
[[nodiscard]] const CrossChainConfig& config() const noexcept { return config_; }
private:
CrossChainConfig config_;
std::shared_ptr<WarpClient> warp_client_;
std::shared_ptr<TeleportClient> teleport_client_;
mutable std::mutex warp_mutex_;
mutable std::mutex teleport_mutex_;
};
} // namespace lx::trading::arbitrage
@@ -1,128 +0,0 @@
// LX Trading SDK - LX-First Arbitrage Strategy
//
// Key Insight: LX DEX is the FASTEST venue (nanosecond price updates, 200ms blocks).
// By the time other venues update, LX has already moved.
//
// This means:
// 1. LX DEX price is the "TRUE" price (most current)
// 2. Other venues are always STALE by comparison
// 3. Arbitrage = correcting stale venues to match LX
// 4. LX DEX is the ORACLE, not just another venue
//
// Strategy:
// 1. Watch LX DEX prices (the reference)
// 2. Compare against "slow" venues (CEX, external DEX)
// 3. When slow venue diverges from LX, trade on SLOW venue
// 4. You're essentially front-running slow venues with LX information
//
// Example:
// - LX DEX BTC: $50,000 (current, true)
// - Binance BTC: $49,990 (stale, 50ms behind)
// - Uniswap BTC: $50,020 (stale, 12s behind)
//
// Action:
// - Buy on Binance at $49,990 (they haven't caught up yet)
// - Sell on Uniswap at $50,020 (they haven't corrected yet)
// - Net: $30 profit per BTC
//
// Why LX wins: By the time Binance/Uniswap update, we've already executed.
#pragma once
#include <lx/trading/arbitrage/types.hpp>
#include <atomic>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <vector>
namespace lx::trading::arbitrage {
/// LX-first arbitrage using LX DEX as the price oracle
class LxFirstArbitrage {
public:
explicit LxFirstArbitrage(LxFirstConfig config);
~LxFirstArbitrage() = default;
// Non-copyable
LxFirstArbitrage(const LxFirstArbitrage&) = delete;
LxFirstArbitrage& operator=(const LxFirstArbitrage&) = delete;
/// Update the LX DEX price (the oracle)
/// Immediately checks for opportunities against stale venues
void update_lx_price(const LxPrice& price);
/// Update a price from a 'slow' venue
void update_venue_price(const VenuePrice& price);
/// Subscribe to opportunity events
void on_opportunity(LxFirstCallback callback);
/// Start the arbitrage system
void start();
/// Stop the arbitrage system
void stop();
/// Check if system is running
[[nodiscard]] bool is_running() const noexcept { return running_.load(); }
/// Get current config
[[nodiscard]] const LxFirstConfig& config() const noexcept { return config_; }
private:
void check_opportunities(const std::string& symbol);
LxFirstOpportunity create_opportunity(
const std::string& symbol,
const LxPrice& lx_price,
const VenuePrice& vp,
int64_t staleness,
const std::string& side,
Decimal divergence,
Decimal divergence_bps) const;
double calculate_confidence(int64_t staleness, Decimal divergence_bps) const;
LxFirstConfig config_;
std::unordered_map<std::string, LxPrice> lx_prices_;
std::unordered_map<std::string, std::vector<VenuePrice>> venue_prices_;
std::vector<LxFirstCallback> callbacks_;
mutable std::mutex lx_prices_mutex_;
mutable std::mutex venue_prices_mutex_;
mutable std::mutex callbacks_mutex_;
std::atomic<bool> running_{false};
};
/*
TRADING EXECUTION STRATEGY
When an LxFirstOpportunity is detected:
1. DO NOT trade on LX DEX (it's the reference, not the opportunity)
2. Trade on the STALE venue:
- If Side="buy": Buy on stale venue (their ask is behind LX)
- If Side="sell": Sell on stale venue (their bid is behind LX)
3. Settlement options:
a) Hold position until venues converge (market neutral)
b) Immediately hedge on LX DEX (lock in profit)
c) Bridge and sell on another venue (more complex)
4. The key insight:
- You're NOT arbitraging between two venues
- You're front-running the slow venue with LX information
- LX price is where the slow venue WILL BE, you just got there first
Example execution:
LX DEX shows BTC = $50,000 (current, true price)
Binance shows BTC = $49,950 (50ms stale)
Action: BUY on Binance at $49,950
Why: Binance WILL update to ~$50,000, we bought before they did
Profit: ~$50 per BTC (0.1%)
Optional hedge: SELL on LX DEX at $50,000 to lock in profit immediately
*/
} // namespace lx::trading::arbitrage
@@ -1,82 +0,0 @@
// LX Trading SDK - Arbitrage Scanner
// Continuously scans for arbitrage opportunities across all venues.
// Supports simple, triangular, and CEX-DEX arbitrage detection.
#pragma once
#include <lx/trading/arbitrage/types.hpp>
#include <atomic>
#include <memory>
#include <mutex>
#include <set>
#include <thread>
#include <unordered_map>
#include <vector>
namespace lx::trading::arbitrage {
/// Known CEX venues
inline const std::set<std::string> CEX_VENUES = {
"binance", "coinbase", "kraken", "okx", "bybit",
"kucoin", "mexc", "gate", "huobi"
};
/// Arbitrage scanner for detecting cross-venue opportunities
class Scanner {
public:
explicit Scanner(ScannerConfig config);
~Scanner();
// Non-copyable
Scanner(const Scanner&) = delete;
Scanner& operator=(const Scanner&) = delete;
/// Add a chain configuration
void add_chain(const CrossChainInfo& info);
/// Update a price feed
void update_price(const PriceSource& source);
/// Subscribe to opportunity events
void on_opportunity(OpportunityCallback callback);
/// Start scanning for opportunities
void start();
/// Stop scanning
void stop();
/// Check if scanner is running
[[nodiscard]] bool is_running() const noexcept { return running_.load(); }
/// Get current config
[[nodiscard]] const ScannerConfig& config() const noexcept { return config_; }
private:
void scan_loop();
std::vector<ArbitrageOpportunity> scan();
std::vector<ArbitrageOpportunity> find_simple_arb(
const std::string& symbol,
const std::vector<PriceSource>& sources) const;
std::vector<ArbitrageOpportunity> find_cex_dex_arb(
const std::string& symbol,
const std::vector<PriceSource>& sources) const;
std::pair<Decimal, Decimal> calculate_costs(
const std::string& source_chain,
const std::string& dest_chain) const;
double calculate_confidence(
const PriceSource& buy,
const PriceSource& sell) const;
ScannerConfig config_;
std::unordered_map<std::string, std::vector<PriceSource>> prices_;
std::unordered_map<std::string, CrossChainInfo> chains_;
std::vector<OpportunityCallback> callbacks_;
mutable std::mutex prices_mutex_;
mutable std::mutex chains_mutex_;
mutable std::mutex callbacks_mutex_;
std::atomic<bool> running_{false};
std::unique_ptr<std::thread> scan_thread_;
};
} // namespace lx::trading::arbitrage
@@ -1,347 +0,0 @@
// LX Trading SDK - Arbitrage Types
// LX-First Strategy: LX DEX is the price oracle
//
// Key Insight: LX DEX is the FASTEST venue (nanosecond updates, 200ms blocks)
// By the time other venues update, LX has already moved.
// LX DEX price is the "TRUTH" - other venues are always STALE.
// Arbitrage = correcting stale venues to match LX
#pragma once
#include <lx/trading/types.hpp>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace lx::trading::arbitrage {
/// Cross-chain transport protocol
enum class CrossChainTransport : uint8_t {
/// Warp - Lux native messaging between subnets (<500ms)
Warp = 0,
/// Teleport - EVM bridge for external chains (~30s)
Teleport = 1,
/// Direct - Same chain, no bridge needed
Direct = 2,
/// CEX API - API calls for centralized exchanges
CexApi = 3
};
inline constexpr const char* to_string(CrossChainTransport t) noexcept {
switch (t) {
case CrossChainTransport::Warp: return "warp";
case CrossChainTransport::Teleport: return "teleport";
case CrossChainTransport::Direct: return "direct";
case CrossChainTransport::CexApi: return "cex_api";
}
return "unknown";
}
/// Type of blockchain
enum class ChainType : uint8_t {
/// Lux subnet (Warp-enabled)
LuxSubnet = 0,
/// EVM-compatible chain
Evm = 1,
/// Centralized exchange
Cex = 2
};
inline constexpr const char* to_string(ChainType t) noexcept {
switch (t) {
case ChainType::LuxSubnet: return "lux_subnet";
case ChainType::Evm: return "evm";
case ChainType::Cex: return "cex";
}
return "unknown";
}
/// Type of arbitrage
enum class ArbType : uint8_t {
/// Simple buy-low-sell-high
Simple = 0,
/// Triangular A->B->C->A
Triangular = 1,
/// Multi-hop complex routes
MultiHop = 2,
/// CEX-DEX arbitrage
CexDex = 3,
/// DEX flash swap
FlashSwap = 4
};
inline constexpr const char* to_string(ArbType t) noexcept {
switch (t) {
case ArbType::Simple: return "simple";
case ArbType::Triangular: return "triangular";
case ArbType::MultiHop: return "multi_hop";
case ArbType::CexDex: return "cex_dex";
case ArbType::FlashSwap: return "flash_swap";
}
return "unknown";
}
/// Price feed from a specific venue/chain
struct PriceSource {
std::string chain_id;
std::string venue;
std::string symbol;
Decimal bid;
Decimal ask;
Decimal liquidity;
int64_t timestamp{0}; // Unix timestamp in milliseconds
int64_t latency{0}; // Latency in milliseconds
[[nodiscard]] Decimal mid_price() const noexcept {
return (bid + ask) / Decimal::from_double(2.0);
}
[[nodiscard]] Decimal spread() const noexcept {
return ask - bid;
}
[[nodiscard]] Decimal spread_bps() const noexcept {
if (bid.is_zero()) return Decimal::zero();
return ((ask - bid) / bid) * Decimal::from_double(10000.0);
}
};
/// LX DEX price - the reference/oracle
struct LxPrice {
std::string symbol;
Decimal bid;
Decimal ask;
Decimal mid;
int64_t timestamp{0};
uint64_t block_num{0};
static LxPrice create(const std::string& sym, Decimal b, Decimal a, int64_t ts, uint64_t block) {
LxPrice price;
price.symbol = sym;
price.bid = b;
price.ask = a;
price.mid = (b + a) / Decimal::from_double(2.0);
price.timestamp = ts;
price.block_num = block;
return price;
}
};
/// Price from a 'slow' venue
struct VenuePrice {
std::string venue;
std::string symbol;
Decimal bid;
Decimal ask;
int64_t timestamp{0};
int64_t latency{0}; // How far behind LX this venue typically is (ms)
bool stale{false}; // Is this price stale relative to LX?
};
/// Single leg of an arbitrage
struct Route {
std::string chain_id;
std::string venue;
std::string action; // "buy" or "sell"
std::string token_in;
std::string token_out;
Decimal amount_in;
Decimal expected_out;
Decimal min_amount_out;
std::vector<uint8_t> swap_data;
};
/// Detected arbitrage opportunity
struct ArbitrageOpportunity {
std::string id;
ArbType arb_type{ArbType::Simple};
std::vector<Route> routes;
PriceSource buy_source;
PriceSource sell_source;
Decimal spread_bps; // Spread in basis points
Decimal estimated_pnl;
Decimal max_size; // Limited by liquidity
Decimal gas_cost_usd;
Decimal bridge_cost_usd;
Decimal net_pnl;
double confidence{0.0}; // 0-1, based on price freshness and liquidity
int64_t expires_at{0};
[[nodiscard]] bool is_profitable() const noexcept {
return net_pnl.is_positive();
}
[[nodiscard]] bool is_expired(int64_t now) const noexcept {
return now > expires_at;
}
};
/// LX-first arbitrage opportunity
struct LxFirstOpportunity {
std::string id;
std::string symbol;
int64_t timestamp{0};
LxPrice lx_price;
std::string stale_venue;
VenuePrice stale_price;
int64_t staleness{0}; // Staleness in milliseconds
std::string side; // "buy" or "sell"
Decimal divergence;
Decimal divergence_bps;
Decimal expected_profit;
Decimal max_size;
double confidence{0.0};
};
/// Unified arbitrage opportunity across venues
struct UnifiedOpportunity {
std::string id;
std::string symbol;
int64_t timestamp{0};
int64_t expires_at{0};
std::string buy_venue;
Decimal buy_price;
Decimal buy_size;
std::string sell_venue;
Decimal sell_price;
Decimal sell_size;
Decimal spread;
Decimal spread_bps;
Decimal max_size;
Decimal gross_profit;
Decimal est_fees;
Decimal net_profit;
double confidence{0.0};
int64_t latency{0};
};
/// Executed arbitrage result
struct UnifiedExecution {
std::string id;
UnifiedOpportunity opportunity;
int64_t start_time{0};
int64_t end_time{0};
std::string status; // "executing", "completed", "failed"
std::optional<std::string> buy_order_id;
std::optional<std::string> sell_order_id;
Decimal actual_profit;
Decimal fees;
std::optional<std::string> error;
};
/// Arbitrage statistics
struct UnifiedArbStats {
uint64_t total_executions{0};
uint64_t successful_executions{0};
Decimal total_pnl;
double win_rate{0.0};
};
/// Configuration for unified arbitrage system
struct UnifiedArbConfig {
Decimal min_spread_bps{Decimal::from_double(10.0)};
Decimal min_profit{Decimal::from_double(5.0)};
Decimal max_position_size{Decimal::from_double(10000.0)};
Decimal max_total_exposure{Decimal::from_double(100000.0)};
std::vector<std::string> symbols{"BTC-USDC", "ETH-USDC", "LUX-USDC"};
std::vector<std::string> venue_priority{"lx_dex", "binance", "mexc", "lx_amm"};
uint64_t scan_interval_ms{100};
uint64_t execute_timeout_ms{5000};
Decimal max_daily_loss{Decimal::from_double(1000.0)};
uint32_t max_trades_per_day{100};
static UnifiedArbConfig defaults() {
return UnifiedArbConfig{};
}
};
/// Configuration for LX-first strategy
struct LxFirstConfig {
int64_t max_staleness_ms{2000};
Decimal min_divergence_bps{Decimal::from_double(10.0)};
Decimal min_profit{Decimal::from_double(5.0)};
Decimal max_position_size{Decimal::from_double(1000.0)};
std::vector<std::string> symbols{"BTC-USDC", "ETH-USDC", "LUX-USDC"};
std::map<std::string, int64_t> venue_latencies{
{"binance", 50},
{"mexc", 100},
{"okx", 80},
{"uniswap", 12000},
{"pancakeswap", 3000}
};
static LxFirstConfig defaults() {
return LxFirstConfig{};
}
};
/// Configuration for arbitrage scanner
struct ScannerConfig {
Decimal min_spread_bps{Decimal::from_double(10.0)};
Decimal min_profit_usd{Decimal::from_double(10.0)};
int64_t max_price_age_ms{5000};
std::vector<std::string> symbols{"BTC", "ETH", "LUX", "SOL", "AVAX"};
std::vector<std::string> chain_ids{"lux", "ethereum", "bsc", "arbitrum", "polygon"};
uint64_t scan_interval_ms{100};
size_t max_concurrency{50};
static ScannerConfig defaults() {
return ScannerConfig{};
}
};
/// Information about a chain
struct CrossChainInfo {
std::string chain_id;
std::string name;
ChainType chain_type{ChainType::LuxSubnet};
uint64_t block_time_ms{0};
uint64_t finality_ms{0};
bool warp_supported{false};
bool teleport_supported{false};
std::vector<std::string> venues;
};
/// Configuration for cross-chain routing
struct CrossChainConfig {
bool warp_enabled{true};
std::optional<std::string> warp_endpoint;
uint64_t warp_timeout_ms{5000};
bool teleport_enabled{true};
std::optional<std::string> teleport_endpoint;
uint64_t teleport_timeout_ms{60000};
std::map<std::string, CrossChainInfo> chains;
static CrossChainConfig defaults();
};
/// Enhanced opportunity with routing information
struct EnhancedOpportunity {
UnifiedOpportunity base;
CrossChainTransport transport{CrossChainTransport::Direct};
int64_t estimated_latency{0};
Decimal bridge_cost;
Decimal adjusted_net_profit;
};
/// Bridge transaction status
struct BridgeStatus {
std::string tx_id;
std::string status; // pending, confirming, completed, failed
std::string source_chain;
std::string dest_chain;
Decimal amount;
Decimal fee;
std::string source_tx;
std::optional<std::string> dest_tx;
int64_t timestamp{0};
};
// Callback types
using OpportunityCallback = std::function<void(const ArbitrageOpportunity&)>;
using LxFirstCallback = std::function<void(const LxFirstOpportunity&)>;
using UnifiedCallback = std::function<void(const UnifiedOpportunity&)>;
} // namespace lx::trading::arbitrage
@@ -1,103 +0,0 @@
// LX Trading SDK - Unified Liquidity Arbitrage
//
// Since LX DEX is the FASTEST venue (nanosecond updates, 200ms blocks),
// it becomes the price ORACLE. Other venues are always stale by comparison.
//
// Architecture:
// 1. LX DEX prices are the TRUTH (most current)
// 2. Other venues (CEX, external DEX) are STALE
// 3. Arbitrage = exploiting stale venues before they catch up
// 4. LX always wins because it sees/moves prices first
//
// NO SMART CONTRACTS - just coordinated trades through unified SDK.
#pragma once
#include <lx/trading/arbitrage/types.hpp>
#include <lx/trading/types.hpp>
#include <atomic>
#include <deque>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
namespace lx::trading::arbitrage {
/// Aggregated orderbook level
struct AggregatedLevel {
Decimal price;
Decimal quantity;
std::string venue;
int64_t timestamp{0};
};
/// Aggregated orderbook from all venues
struct AggregatedBook {
std::string symbol;
std::vector<AggregatedLevel> bids;
std::vector<AggregatedLevel> asks;
};
/// Trading client interface for arbitrage
class TradingClient {
public:
virtual ~TradingClient() = default;
/// Get aggregated orderbook from all venues
virtual AggregatedBook aggregated_orderbook(const std::string& symbol) = 0;
/// Place an order on a specific venue
virtual Order place_order(const OrderRequest& request) = 0;
};
/// Unified arbitrage across all SDK-connected venues
class UnifiedArbitrage {
public:
UnifiedArbitrage(std::shared_ptr<TradingClient> client, UnifiedArbConfig config);
~UnifiedArbitrage();
// Non-copyable
UnifiedArbitrage(const UnifiedArbitrage&) = delete;
UnifiedArbitrage& operator=(const UnifiedArbitrage&) = delete;
/// Start the arbitrage system
void start();
/// Stop the arbitrage system
void stop();
/// Subscribe to opportunity events
void on_opportunity(UnifiedCallback callback);
/// Get arbitrage statistics
[[nodiscard]] UnifiedArbStats get_stats() const;
/// Check if system is running
[[nodiscard]] bool is_running() const noexcept { return running_.load(); }
/// Get current config
[[nodiscard]] const UnifiedArbConfig& config() const noexcept { return config_; }
private:
void scan_loop();
void execute_loop();
std::optional<UnifiedOpportunity> find_opportunity(const std::string& symbol);
UnifiedExecution execute_opportunity(const UnifiedOpportunity& opp);
std::shared_ptr<TradingClient> client_;
UnifiedArbConfig config_;
Decimal total_pnl_;
std::vector<UnifiedExecution> executions_;
std::vector<UnifiedCallback> callbacks_;
std::deque<UnifiedOpportunity> opportunity_queue_;
mutable std::mutex pnl_mutex_;
mutable std::mutex executions_mutex_;
mutable std::mutex callbacks_mutex_;
mutable std::mutex queue_mutex_;
std::atomic<bool> running_{false};
std::unique_ptr<std::thread> scan_thread_;
std::unique_ptr<std::thread> execute_thread_;
};
} // namespace lx::trading::arbitrage
@@ -1,206 +0,0 @@
// LX Trading SDK - Unified Client
// Smart routing across multiple venues
#pragma once
#include <lx/trading/adapter.hpp>
#include <lx/trading/config.hpp>
#include <lx/trading/orderbook.hpp>
#include <future>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
namespace lx::trading {
// Forward declarations
class RiskManager;
// Unified trading client
class Client {
public:
explicit Client(const Config& config);
~Client();
// Disallow copy
Client(const Client&) = delete;
Client& operator=(const Client&) = delete;
// Connection management
std::future<void> connect();
std::future<void> disconnect();
// Venue access
[[nodiscard]] VenueAdapter* venue(std::string_view name);
[[nodiscard]] const VenueAdapter* venue(std::string_view name) const;
[[nodiscard]] std::vector<VenueInfo> venues() const;
// =========================================================================
// Market Data
// =========================================================================
// Get orderbook from specific venue
std::future<std::unique_ptr<Orderbook>> orderbook(
const std::string& symbol,
std::optional<std::string_view> venue = std::nullopt);
// Get aggregated orderbook from all venues
std::future<AggregatedOrderbook> aggregated_orderbook(const std::string& symbol);
// Get ticker from specific venue
std::future<Ticker> ticker(
const std::string& symbol,
std::optional<std::string_view> venue = std::nullopt);
// Get tickers from all venues
std::future<std::vector<Ticker>> tickers(const std::string& symbol);
// =========================================================================
// Account
// =========================================================================
// Get aggregated balances across all venues
std::future<std::vector<AggregatedBalance>> balances();
// Get balance for specific asset from venue
std::future<Balance> balance(
const std::string& asset,
std::optional<std::string_view> venue = std::nullopt);
// =========================================================================
// Order Management
// =========================================================================
// Place market buy order
std::future<Order> buy(
const std::string& symbol,
Decimal quantity,
std::optional<std::string_view> venue = std::nullopt);
// Place market sell order
std::future<Order> sell(
const std::string& symbol,
Decimal quantity,
std::optional<std::string_view> venue = std::nullopt);
// Place limit buy order
std::future<Order> limit_buy(
const std::string& symbol,
Decimal quantity,
Decimal price,
std::optional<std::string_view> venue = std::nullopt);
// Place limit sell order
std::future<Order> limit_sell(
const std::string& symbol,
Decimal quantity,
Decimal price,
std::optional<std::string_view> venue = std::nullopt);
// Place order with full control
std::future<Order> place_order(const OrderRequest& request);
// Cancel order
std::future<Order> cancel_order(
const std::string& order_id,
const std::string& symbol,
const std::string& venue);
// Cancel all orders
std::future<std::vector<Order>> cancel_all_orders(
std::optional<std::string_view> symbol = std::nullopt,
std::optional<std::string_view> venue = std::nullopt);
// Get all open orders
std::future<std::vector<Order>> open_orders(
std::optional<std::string_view> symbol = std::nullopt);
// =========================================================================
// AMM Operations
// =========================================================================
// Get swap quote
std::future<SwapQuote> quote(
const std::string& base_token,
const std::string& quote_token,
Decimal amount,
bool is_buy,
const std::string& venue);
// Execute swap
std::future<Trade> swap(
const std::string& base_token,
const std::string& quote_token,
Decimal amount,
bool is_buy,
double slippage,
const std::string& venue);
// Get pool info
std::future<PoolInfo> pool_info(
const std::string& base_token,
const std::string& quote_token,
const std::string& venue);
// Add liquidity
std::future<LiquidityResult> add_liquidity(
const std::string& base_token,
const std::string& quote_token,
Decimal base_amount,
Decimal quote_amount,
double slippage,
const std::string& venue);
// Remove liquidity
std::future<LiquidityResult> remove_liquidity(
const std::string& pool_address,
Decimal liquidity_amount,
double slippage,
const std::string& venue);
// Get LP positions
std::future<std::vector<LpPosition>> lp_positions(const std::string& venue);
// =========================================================================
// Streaming
// =========================================================================
void subscribe_ticker(
const std::string& symbol,
VenueAdapter::TickerCallback callback,
std::optional<std::string_view> venue = std::nullopt);
void subscribe_trades(
const std::string& symbol,
VenueAdapter::TradeCallback callback,
std::optional<std::string_view> venue = std::nullopt);
void subscribe_orderbook(
const std::string& symbol,
VenueAdapter::OrderbookCallback callback,
std::optional<std::string_view> venue = std::nullopt);
void subscribe_orders(VenueAdapter::OrderCallback callback);
void unsubscribe_all();
// =========================================================================
// Risk Management
// =========================================================================
[[nodiscard]] RiskManager& risk_manager() { return *risk_manager_; }
[[nodiscard]] const RiskManager& risk_manager() const { return *risk_manager_; }
private:
Order place_order_impl(const OrderRequest& request);
Order smart_route(const OrderRequest& request);
VenueAdapter* get_venue(std::optional<std::string_view> name);
Config config_;
std::unordered_map<std::string, std::unique_ptr<VenueAdapter>> venues_;
std::optional<std::string> default_venue_;
std::unique_ptr<RiskManager> risk_manager_;
};
} // namespace lx::trading
@@ -1,244 +0,0 @@
// LX Trading SDK - Configuration
// Builder pattern for fluent configuration
#pragma once
#include <lx/trading/types.hpp>
#include <string>
#include <unordered_map>
#include <vector>
namespace lx::trading {
// General SDK settings
struct GeneralConfig {
std::string log_level = "info";
int timeout_ms = 30000;
bool smart_routing = true;
std::vector<std::string> venue_priority;
int min_improvement_bps = 5;
};
// Risk management settings
struct RiskConfig {
bool enabled = true;
Decimal max_position_size;
Decimal max_order_size;
Decimal max_daily_loss;
int max_open_orders = 100;
bool kill_switch_enabled = false;
std::unordered_map<std::string, Decimal> position_limits;
};
// Native venue config (LX DEX or LX AMM)
class NativeVenueConfig {
public:
std::string venue_type = "dex"; // "dex" or "amm"
std::string api_url;
std::optional<std::string> ws_url;
std::optional<std::string> api_key;
std::optional<std::string> api_secret;
std::optional<std::string> wallet_address;
std::optional<std::string> private_key;
std::string network = "mainnet";
int chain_id = 96369;
bool streaming = true;
std::optional<Decimal> maker_fee;
std::optional<Decimal> taker_fee;
NativeVenueConfig() = default;
static NativeVenueConfig lx_dex(std::string_view api_url) {
NativeVenueConfig cfg;
cfg.venue_type = "dex";
cfg.api_url = std::string(api_url);
return cfg;
}
static NativeVenueConfig lx_amm(std::string_view api_url) {
NativeVenueConfig cfg;
cfg.venue_type = "amm";
cfg.api_url = std::string(api_url);
return cfg;
}
NativeVenueConfig& with_credentials(std::string_view key, std::string_view secret) {
api_key = std::string(key);
api_secret = std::string(secret);
return *this;
}
NativeVenueConfig& with_wallet(std::string_view address, std::string_view priv_key) {
wallet_address = std::string(address);
private_key = std::string(priv_key);
return *this;
}
NativeVenueConfig& with_ws(std::string_view url) {
ws_url = std::string(url);
return *this;
}
NativeVenueConfig& testnet() {
network = "testnet";
chain_id = 8888;
return *this;
}
};
// CCXT exchange config
class CcxtConfig {
public:
std::string exchange_id;
std::optional<std::string> api_key;
std::optional<std::string> api_secret;
std::optional<std::string> password;
bool sandbox = false;
bool rate_limit = true;
std::unordered_map<std::string, std::string> options;
CcxtConfig() = default;
static CcxtConfig create(std::string_view exchange) {
CcxtConfig cfg;
cfg.exchange_id = std::string(exchange);
return cfg;
}
CcxtConfig& with_credentials(std::string_view key, std::string_view secret) {
api_key = std::string(key);
api_secret = std::string(secret);
return *this;
}
CcxtConfig& with_password(std::string_view pwd) {
password = std::string(pwd);
return *this;
}
CcxtConfig& enable_sandbox() {
sandbox = true;
return *this;
}
CcxtConfig& with_option(std::string_view key, std::string_view value) {
options[std::string(key)] = std::string(value);
return *this;
}
};
// Hummingbot Gateway config
class HummingbotConfig {
public:
std::string host = "localhost";
int port = 15888;
bool https = false;
std::string connector;
std::string chain = "lux";
std::string network = "mainnet";
std::optional<std::string> wallet_address;
HummingbotConfig() = default;
static HummingbotConfig create(std::string_view conn) {
HummingbotConfig cfg;
cfg.connector = std::string(conn);
return cfg;
}
HummingbotConfig& with_wallet(std::string_view address) {
wallet_address = std::string(address);
return *this;
}
HummingbotConfig& with_endpoint(std::string_view h, int p) {
host = std::string(h);
port = p;
return *this;
}
HummingbotConfig& enable_https() {
https = true;
return *this;
}
[[nodiscard]] std::string base_url() const {
return (https ? "https://" : "http://") + host + ":" + std::to_string(port);
}
};
// Main SDK configuration
class Config {
public:
GeneralConfig general;
RiskConfig risk;
std::unordered_map<std::string, NativeVenueConfig> native;
std::unordered_map<std::string, CcxtConfig> ccxt;
std::unordered_map<std::string, HummingbotConfig> hummingbot;
Config() = default;
// Load from TOML file
static Config from_file(std::string_view path);
// Load from TOML string
static Config from_toml(std::string_view content);
// Builder methods
Config& with_native(std::string_view name, NativeVenueConfig cfg) {
native[std::string(name)] = std::move(cfg);
return *this;
}
Config& with_ccxt(std::string_view name, CcxtConfig cfg) {
ccxt[std::string(name)] = std::move(cfg);
return *this;
}
Config& with_hummingbot(std::string_view name, HummingbotConfig cfg) {
hummingbot[std::string(name)] = std::move(cfg);
return *this;
}
Config& enable_smart_routing(bool enabled = true) {
general.smart_routing = enabled;
return *this;
}
Config& set_timeout(int ms) {
general.timeout_ms = ms;
return *this;
}
Config& set_venue_priority(std::vector<std::string> priority) {
general.venue_priority = std::move(priority);
return *this;
}
Config& enable_risk_management(bool enabled = true) {
risk.enabled = enabled;
return *this;
}
Config& set_max_order_size(Decimal size) {
risk.max_order_size = size;
return *this;
}
Config& set_max_position_size(Decimal size) {
risk.max_position_size = size;
return *this;
}
Config& set_max_daily_loss(Decimal loss) {
risk.max_daily_loss = loss;
return *this;
}
Config& set_position_limit(std::string_view asset, Decimal limit) {
risk.position_limits[std::string(asset)] = limit;
return *this;
}
};
} // namespace lx::trading
@@ -1,225 +0,0 @@
// LX Trading SDK - Execution Algorithms
// TWAP, VWAP, Iceberg, Sniper execution strategies
#pragma once
#include <lx/trading/types.hpp>
#include <chrono>
#include <functional>
#include <future>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace lx::trading {
// Forward declaration
class Client;
// Execution result
struct ExecutionResult {
std::vector<Order> orders;
Decimal total_quantity;
Decimal total_filled;
std::optional<Decimal> average_price;
int64_t start_time;
int64_t end_time;
bool completed;
std::string error;
[[nodiscard]] Decimal fill_rate() const {
if (total_quantity.is_zero()) return Decimal::zero();
return total_filled / total_quantity;
}
[[nodiscard]] int64_t duration_ms() const {
return end_time - start_time;
}
};
// Progress callback
using ExecutionCallback = std::function<void(const Order&, Decimal remaining)>;
// Base executor interface
class Executor {
public:
virtual ~Executor() = default;
virtual std::future<ExecutionResult> execute() = 0;
virtual void cancel() = 0;
virtual void set_callback(ExecutionCallback cb) { callback_ = std::move(cb); }
protected:
ExecutionCallback callback_;
};
// TWAP - Time-Weighted Average Price
class TwapExecutor : public Executor {
public:
TwapExecutor(
Client& client,
std::string symbol,
Side side,
Decimal total_quantity,
std::chrono::seconds duration,
int num_slices);
std::future<ExecutionResult> execute() override;
void cancel() override;
private:
Client& client_;
std::string symbol_;
Side side_;
Decimal total_quantity_;
std::chrono::seconds duration_;
int num_slices_;
std::atomic<bool> cancelled_{false};
};
// VWAP - Volume-Weighted Average Price
class VwapExecutor : public Executor {
public:
VwapExecutor(
Client& client,
std::string symbol,
Side side,
Decimal total_quantity,
Decimal participation_rate, // e.g., 0.1 = 10% of volume
std::chrono::seconds max_duration);
std::future<ExecutionResult> execute() override;
void cancel() override;
private:
Client& client_;
std::string symbol_;
Side side_;
Decimal total_quantity_;
Decimal participation_rate_;
std::chrono::seconds max_duration_;
std::atomic<bool> cancelled_{false};
};
// Iceberg - Hidden large order
class IcebergExecutor : public Executor {
public:
IcebergExecutor(
Client& client,
std::string symbol,
Side side,
Decimal total_quantity,
Decimal visible_quantity,
Decimal price,
std::optional<std::string> venue = std::nullopt);
std::future<ExecutionResult> execute() override;
void cancel() override;
private:
Client& client_;
std::string symbol_;
Side side_;
Decimal total_quantity_;
Decimal visible_quantity_;
Decimal price_;
std::optional<std::string> venue_;
std::atomic<bool> cancelled_{false};
};
// Sniper - Wait for price target then execute
class SniperExecutor : public Executor {
public:
SniperExecutor(
Client& client,
std::string symbol,
Side side,
Decimal quantity,
Decimal target_price,
std::chrono::seconds timeout);
std::future<ExecutionResult> execute() override;
void cancel() override;
private:
Client& client_;
std::string symbol_;
Side side_;
Decimal quantity_;
Decimal target_price_;
std::chrono::seconds timeout_;
std::atomic<bool> cancelled_{false};
};
// POV - Percentage of Volume
class PovExecutor : public Executor {
public:
PovExecutor(
Client& client,
std::string symbol,
Side side,
Decimal total_quantity,
Decimal target_participation, // e.g., 0.15 = 15% of volume
std::chrono::seconds max_duration,
std::optional<Decimal> price_limit = std::nullopt);
std::future<ExecutionResult> execute() override;
void cancel() override;
private:
Client& client_;
std::string symbol_;
Side side_;
Decimal total_quantity_;
Decimal target_participation_;
std::chrono::seconds max_duration_;
std::optional<Decimal> price_limit_;
std::atomic<bool> cancelled_{false};
};
// Factory functions for convenience
inline std::unique_ptr<TwapExecutor> make_twap(
Client& client,
std::string symbol,
Side side,
Decimal quantity,
std::chrono::seconds duration,
int slices = 10) {
return std::make_unique<TwapExecutor>(
client, std::move(symbol), side, quantity, duration, slices);
}
inline std::unique_ptr<VwapExecutor> make_vwap(
Client& client,
std::string symbol,
Side side,
Decimal quantity,
Decimal participation = Decimal::from_double(0.1),
std::chrono::seconds max_duration = std::chrono::seconds(3600)) {
return std::make_unique<VwapExecutor>(
client, std::move(symbol), side, quantity, participation, max_duration);
}
inline std::unique_ptr<IcebergExecutor> make_iceberg(
Client& client,
std::string symbol,
Side side,
Decimal total_quantity,
Decimal visible_quantity,
Decimal price) {
return std::make_unique<IcebergExecutor>(
client, std::move(symbol), side, total_quantity, visible_quantity, price);
}
inline std::unique_ptr<SniperExecutor> make_sniper(
Client& client,
std::string symbol,
Side side,
Decimal quantity,
Decimal target_price,
std::chrono::seconds timeout = std::chrono::seconds(60)) {
return std::make_unique<SniperExecutor>(
client, std::move(symbol), side, quantity, target_price, timeout);
}
} // namespace lx::trading
@@ -1,226 +0,0 @@
// LX Trading SDK - Financial Mathematics
// SIMD-optimized options pricing, AMM math, and risk metrics
#pragma once
#include <lx/trading/types.hpp>
#include <cmath>
#include <tuple>
#include <vector>
// SIMD support detection
#if defined(__AVX2__)
#include <immintrin.h>
#define LX_SIMD_AVX2 1
#elif defined(__SSE4_1__)
#include <smmintrin.h>
#define LX_SIMD_SSE4 1
#endif
namespace lx::trading::math {
// =============================================================================
// Constants
// =============================================================================
constexpr double PI = 3.14159265358979323846;
constexpr double SQRT_2PI = 2.506628274631000502;
constexpr double SQRT_2 = 1.41421356237309504880;
// =============================================================================
// Options Pricing - Black-Scholes
// =============================================================================
// Greeks structure
struct Greeks {
double delta;
double gamma;
double theta;
double vega;
double rho;
};
// Standard normal CDF (approximation - Abramowitz & Stegun)
inline double norm_cdf(double x) noexcept {
constexpr double a1 = 0.254829592;
constexpr double a2 = -0.284496736;
constexpr double a3 = 1.421413741;
constexpr double a4 = -1.453152027;
constexpr double a5 = 1.061405429;
constexpr double p = 0.3275911;
int sign = (x < 0) ? -1 : 1;
x = std::abs(x) / SQRT_2;
double t = 1.0 / (1.0 + p * x);
double y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * std::exp(-x * x);
return 0.5 * (1.0 + sign * y);
}
// Standard normal PDF
inline double norm_pdf(double x) noexcept {
return std::exp(-0.5 * x * x) / SQRT_2PI;
}
// Black-Scholes option price
// S: spot, K: strike, T: time (years), r: rate, sigma: vol
double black_scholes(
double S, double K, double T, double r, double sigma,
bool is_call = true) noexcept;
// Implied volatility from option price (Newton-Raphson)
double implied_volatility(
double price, double S, double K, double T, double r,
bool is_call = true, double tol = 1e-6, int max_iter = 100) noexcept;
// Calculate all Greeks
Greeks greeks(
double S, double K, double T, double r, double sigma,
bool is_call = true) noexcept;
// =============================================================================
// AMM Pricing
// =============================================================================
// Constant product AMM (Uniswap V2 style)
// Returns: (output_amount, effective_price)
std::pair<double, double> constant_product_price(
double reserve_x, double reserve_y,
double amount_in, double fee_rate = 0.003,
bool is_x_to_y = true) noexcept;
// Concentrated liquidity (Uniswap V3 style)
// Returns: (output_amount, new_sqrt_price, price_impact)
std::tuple<double, double, double> concentrated_liquidity_price(
double liquidity,
double sqrt_price_current,
double sqrt_price_lower,
double sqrt_price_upper,
double amount_in,
double fee_rate = 0.003,
bool is_token0_in = true) noexcept;
// Calculate liquidity for concentrated position
double calculate_liquidity(
double amount_x, double amount_y,
double sqrt_price_current,
double sqrt_price_lower,
double sqrt_price_upper) noexcept;
// Price conversions
inline double price_to_sqrt_price(double price) noexcept {
return std::sqrt(price);
}
inline double sqrt_price_to_price(double sqrt_price) noexcept {
return sqrt_price * sqrt_price;
}
inline double tick_to_sqrt_price(int tick) noexcept {
return std::pow(1.0001, tick / 2.0);
}
inline int sqrt_price_to_tick(double sqrt_price, int tick_spacing = 60) noexcept {
int tick = static_cast<int>(2.0 * std::log(sqrt_price) / std::log(1.0001));
return (tick / tick_spacing) * tick_spacing;
}
// =============================================================================
// Risk Metrics
// =============================================================================
// Historical volatility
double volatility(
const std::vector<double>& returns,
bool annualize = true,
int periods_per_year = 252) noexcept;
// Sharpe ratio
double sharpe_ratio(
const std::vector<double>& returns,
double risk_free_rate = 0.0,
int periods_per_year = 252) noexcept;
// Sortino ratio (downside deviation)
double sortino_ratio(
const std::vector<double>& returns,
double risk_free_rate = 0.0,
double target_return = 0.0,
int periods_per_year = 252) noexcept;
// Maximum drawdown
// Returns: (max_drawdown, peak_index, trough_index)
std::tuple<double, size_t, size_t> max_drawdown(
const std::vector<double>& prices) noexcept;
// Value at Risk (Historical or Parametric)
double var(
const std::vector<double>& returns,
double confidence = 0.95,
bool parametric = false) noexcept;
// Conditional VaR (Expected Shortfall)
double cvar(
const std::vector<double>& returns,
double confidence = 0.95) noexcept;
// =============================================================================
// SIMD Optimized Operations
// =============================================================================
#ifdef LX_SIMD_AVX2
// Batch Black-Scholes pricing (AVX2)
void black_scholes_batch(
const double* S, const double* K, const double* T,
const double* r, const double* sigma,
double* prices, size_t count, bool is_call = true) noexcept;
// Batch norm_cdf (AVX2)
void norm_cdf_batch(
const double* x, double* result, size_t count) noexcept;
// Vectorized sum (AVX2)
double sum_avx2(const double* data, size_t count) noexcept;
// Vectorized mean (AVX2)
double mean_avx2(const double* data, size_t count) noexcept;
// Vectorized variance (AVX2)
double variance_avx2(const double* data, size_t count, double mean) noexcept;
#endif // LX_SIMD_AVX2
// =============================================================================
// Statistical Utilities
// =============================================================================
// Calculate returns from prices
std::vector<double> calculate_returns(const std::vector<double>& prices);
// Rolling mean
std::vector<double> rolling_mean(const std::vector<double>& data, size_t window);
// Rolling std deviation
std::vector<double> rolling_std(const std::vector<double>& data, size_t window);
// Exponential moving average
std::vector<double> ema(const std::vector<double>& data, double alpha);
// Correlation coefficient
double correlation(
const std::vector<double>& x,
const std::vector<double>& y) noexcept;
// Covariance
double covariance(
const std::vector<double>& x,
const std::vector<double>& y) noexcept;
// Beta coefficient
double beta(
const std::vector<double>& asset_returns,
const std::vector<double>& market_returns) noexcept;
} // namespace lx::trading::math
@@ -1,387 +0,0 @@
// LX Trading SDK - Lock-Free Orderbook
// High-performance orderbook with lock-free updates
#pragma once
#include <lx/trading/types.hpp>
#include <algorithm>
#include <atomic>
#include <memory>
#include <shared_mutex>
#include <string>
#include <unordered_map>
#include <vector>
namespace lx::trading {
// Single venue orderbook with lock-free reads
class Orderbook {
public:
explicit Orderbook(std::string_view symbol, std::string_view venue = "")
: symbol_(symbol), venue_(venue), timestamp_(now_ms()), sequence_(0) {}
Orderbook(const Orderbook&) = delete;
Orderbook& operator=(const Orderbook&) = delete;
Orderbook(Orderbook&&) = default;
Orderbook& operator=(Orderbook&&) = default;
// Accessors
[[nodiscard]] const std::string& symbol() const { return symbol_; }
[[nodiscard]] const std::string& venue() const { return venue_; }
[[nodiscard]] int64_t timestamp() const { return timestamp_.load(std::memory_order_acquire); }
[[nodiscard]] uint64_t sequence() const { return sequence_.load(std::memory_order_acquire); }
// Mutators (thread-safe)
void add_bid(Decimal price, Decimal quantity) {
std::unique_lock lock(mutex_);
bids_.push_back({price, quantity});
}
void add_ask(Decimal price, Decimal quantity) {
std::unique_lock lock(mutex_);
asks_.push_back({price, quantity});
}
void set_bid(Decimal price, Decimal quantity) {
std::unique_lock lock(mutex_);
for (auto& level : bids_) {
if (level.price == price) {
level.quantity = quantity;
return;
}
}
bids_.push_back({price, quantity});
}
void set_ask(Decimal price, Decimal quantity) {
std::unique_lock lock(mutex_);
for (auto& level : asks_) {
if (level.price == price) {
level.quantity = quantity;
return;
}
}
asks_.push_back({price, quantity});
}
void remove_bid(Decimal price) {
std::unique_lock lock(mutex_);
bids_.erase(std::remove_if(bids_.begin(), bids_.end(),
[price](const PriceLevel& l) { return l.price == price; }), bids_.end());
}
void remove_ask(Decimal price) {
std::unique_lock lock(mutex_);
asks_.erase(std::remove_if(asks_.begin(), asks_.end(),
[price](const PriceLevel& l) { return l.price == price; }), asks_.end());
}
void clear() {
std::unique_lock lock(mutex_);
bids_.clear();
asks_.clear();
}
void sort() {
std::unique_lock lock(mutex_);
std::sort(bids_.begin(), bids_.end(),
[](const PriceLevel& a, const PriceLevel& b) { return a.price > b.price; });
std::sort(asks_.begin(), asks_.end(),
[](const PriceLevel& a, const PriceLevel& b) { return a.price < b.price; });
sequence_.fetch_add(1, std::memory_order_release);
timestamp_.store(now_ms(), std::memory_order_release);
}
void set_timestamp(int64_t ts) { timestamp_.store(ts, std::memory_order_release); }
void set_sequence(uint64_t seq) { sequence_.store(seq, std::memory_order_release); }
// Readers (lock-free via snapshot)
[[nodiscard]] std::vector<PriceLevel> bids() const {
std::shared_lock lock(mutex_);
return bids_;
}
[[nodiscard]] std::vector<PriceLevel> asks() const {
std::shared_lock lock(mutex_);
return asks_;
}
[[nodiscard]] std::optional<Decimal> best_bid() const {
std::shared_lock lock(mutex_);
return bids_.empty() ? std::nullopt : std::optional<Decimal>(bids_[0].price);
}
[[nodiscard]] std::optional<Decimal> best_ask() const {
std::shared_lock lock(mutex_);
return asks_.empty() ? std::nullopt : std::optional<Decimal>(asks_[0].price);
}
[[nodiscard]] std::optional<Decimal> mid_price() const {
std::shared_lock lock(mutex_);
if (bids_.empty() || asks_.empty()) return std::nullopt;
return (bids_[0].price + asks_[0].price) / Decimal::from_double(2.0);
}
[[nodiscard]] std::optional<Decimal> spread() const {
std::shared_lock lock(mutex_);
if (bids_.empty() || asks_.empty()) return std::nullopt;
return asks_[0].price - bids_[0].price;
}
[[nodiscard]] std::optional<Decimal> spread_percent() const {
auto s = spread();
auto m = mid_price();
if (!s || !m || m->is_zero()) return std::nullopt;
return (*s / *m) * Decimal::from_double(100.0);
}
[[nodiscard]] Decimal bid_liquidity() const {
std::shared_lock lock(mutex_);
Decimal total;
for (const auto& level : bids_) {
total = total + level.value();
}
return total;
}
[[nodiscard]] Decimal ask_liquidity() const {
std::shared_lock lock(mutex_);
Decimal total;
for (const auto& level : asks_) {
total = total + level.value();
}
return total;
}
[[nodiscard]] Decimal bid_depth(int levels) const {
std::shared_lock lock(mutex_);
Decimal total;
int count = 0;
for (const auto& level : bids_) {
if (count >= levels) break;
total = total + level.value();
++count;
}
return total;
}
[[nodiscard]] Decimal ask_depth(int levels) const {
std::shared_lock lock(mutex_);
Decimal total;
int count = 0;
for (const auto& level : asks_) {
if (count >= levels) break;
total = total + level.value();
++count;
}
return total;
}
// VWAP calculation for buying `amount`
[[nodiscard]] std::optional<Decimal> vwap_buy(Decimal amount) const {
std::shared_lock lock(mutex_);
return calculate_vwap(asks_, amount);
}
// VWAP calculation for selling `amount`
[[nodiscard]] std::optional<Decimal> vwap_sell(Decimal amount) const {
std::shared_lock lock(mutex_);
return calculate_vwap(bids_, amount);
}
// Check if sufficient liquidity exists
[[nodiscard]] bool has_liquidity(Side side, Decimal amount) const {
std::shared_lock lock(mutex_);
const auto& levels = (side == Side::Buy) ? asks_ : bids_;
Decimal total;
for (const auto& level : levels) {
total = total + level.quantity;
}
return total >= amount;
}
private:
[[nodiscard]] static std::optional<Decimal> calculate_vwap(
const std::vector<PriceLevel>& levels, Decimal amount) {
Decimal remaining = amount;
Decimal total_value;
Decimal total_qty;
for (const auto& level : levels) {
if (remaining <= Decimal::zero()) break;
Decimal fill_qty = (remaining < level.quantity) ? remaining : level.quantity;
total_value = total_value + (fill_qty * level.price);
total_qty = total_qty + fill_qty;
remaining = remaining - fill_qty;
}
if (total_qty.is_zero()) return std::nullopt;
return total_value / total_qty;
}
std::string symbol_;
std::string venue_;
std::atomic<int64_t> timestamp_;
std::atomic<uint64_t> sequence_;
mutable std::shared_mutex mutex_;
std::vector<PriceLevel> bids_;
std::vector<PriceLevel> asks_;
};
// Hash functor for Decimal
struct DecimalHash {
size_t operator()(const Decimal& d) const {
return std::hash<int64_t>()(d.scaled_value());
}
};
// Type alias for price-level map
using PriceLevelMap = std::unordered_map<Decimal, std::vector<std::pair<std::string, Decimal>>, DecimalHash>;
// Aggregated orderbook from multiple venues
class AggregatedOrderbook {
public:
explicit AggregatedOrderbook(std::string_view symbol) : symbol_(symbol) {}
[[nodiscard]] const std::string& symbol() const { return symbol_; }
[[nodiscard]] int64_t timestamp() const { return timestamp_; }
void add_orderbook(const Orderbook& book) {
auto bids = book.bids();
auto asks = book.asks();
for (const auto& level : bids) {
bids_[level.price].emplace_back(book.venue(), level.quantity);
}
for (const auto& level : asks) {
asks_[level.price].emplace_back(book.venue(), level.quantity);
}
timestamp_ = std::max(timestamp_, book.timestamp());
}
// Best bid across all venues: (price, venue, qty)
[[nodiscard]] std::optional<std::tuple<Decimal, std::string, Decimal>> best_bid() const {
if (bids_.empty()) return std::nullopt;
auto it = std::max_element(bids_.begin(), bids_.end(),
[](const auto& a, const auto& b) { return a.first < b.first; });
if (it == bids_.end() || it->second.empty()) return std::nullopt;
const auto& [venue, qty] = it->second[0];
return std::make_tuple(it->first, venue, qty);
}
// Best ask across all venues: (price, venue, qty)
[[nodiscard]] std::optional<std::tuple<Decimal, std::string, Decimal>> best_ask() const {
if (asks_.empty()) return std::nullopt;
auto it = std::min_element(asks_.begin(), asks_.end(),
[](const auto& a, const auto& b) { return a.first < b.first; });
if (it == asks_.end() || it->second.empty()) return std::nullopt;
const auto& [venue, qty] = it->second[0];
return std::make_tuple(it->first, venue, qty);
}
// Get aggregated bid levels
[[nodiscard]] std::vector<PriceLevel> aggregated_bids() const {
std::vector<std::pair<Decimal, Decimal>> temp;
for (const auto& [price, venues] : bids_) {
Decimal total_qty;
for (const auto& [v, qty] : venues) {
total_qty = total_qty + qty;
}
temp.emplace_back(price, total_qty);
}
std::sort(temp.begin(), temp.end(),
[](const auto& a, const auto& b) { return a.first > b.first; });
std::vector<PriceLevel> result;
result.reserve(temp.size());
for (const auto& [price, qty] : temp) {
result.push_back({price, qty});
}
return result;
}
// Get aggregated ask levels
[[nodiscard]] std::vector<PriceLevel> aggregated_asks() const {
std::vector<std::pair<Decimal, Decimal>> temp;
for (const auto& [price, venues] : asks_) {
Decimal total_qty;
for (const auto& [v, qty] : venues) {
total_qty = total_qty + qty;
}
temp.emplace_back(price, total_qty);
}
std::sort(temp.begin(), temp.end(),
[](const auto& a, const auto& b) { return a.first < b.first; });
std::vector<PriceLevel> result;
result.reserve(temp.size());
for (const auto& [price, qty] : temp) {
result.push_back({price, qty});
}
return result;
}
// Find best venue for buying amount: (venue, price)
[[nodiscard]] std::optional<std::pair<std::string, Decimal>> best_venue_buy(Decimal amount) const {
if (asks_.empty()) return std::nullopt;
// Use the same direct iteration approach as aggregated_asks
// Find the lowest price ask level
std::optional<Decimal> best_price;
std::string best_venue;
for (const auto& [price, venues] : asks_) {
if ((!best_price || price < *best_price) && !venues.empty()) {
best_price = price;
best_venue = venues[0].first;
}
}
if (best_venue.empty() || !best_price) return std::nullopt;
return std::make_pair(best_venue, *best_price);
}
// Find best venue for selling amount: (venue, price)
[[nodiscard]] std::optional<std::pair<std::string, Decimal>> best_venue_sell(Decimal amount) const {
if (bids_.empty()) return std::nullopt;
// Find the highest price bid level
std::optional<Decimal> best_price;
std::string best_venue;
for (const auto& [price, venues] : bids_) {
if ((!best_price || price > *best_price) && !venues.empty()) {
best_price = price;
best_venue = venues[0].first;
}
}
if (best_venue.empty() || !best_price) return std::nullopt;
return std::make_pair(best_venue, *best_price);
}
void clear() {
bids_.clear();
asks_.clear();
timestamp_ = 0;
}
private:
std::string symbol_;
int64_t timestamp_ = 0;
// price -> [(venue, qty), ...]
PriceLevelMap bids_;
PriceLevelMap asks_;
};
} // namespace lx::trading
@@ -1,97 +0,0 @@
// LX Trading SDK - Risk Management
// Thread-safe risk controls and position tracking
#pragma once
#include <lx/trading/config.hpp>
#include <lx/trading/types.hpp>
#include <atomic>
#include <mutex>
#include <shared_mutex>
#include <stdexcept>
#include <string>
#include <unordered_map>
namespace lx::trading {
// Risk limit violation exception
class RiskError : public std::runtime_error {
public:
explicit RiskError(const std::string& msg) : std::runtime_error(msg) {}
};
// Thread-safe risk manager
class RiskManager {
public:
explicit RiskManager(const RiskConfig& config);
// Configuration
[[nodiscard]] bool is_enabled() const noexcept { return config_.enabled; }
[[nodiscard]] const RiskConfig& config() const noexcept { return config_; }
// Kill switch
[[nodiscard]] bool is_killed() const noexcept { return kill_switch_.load(std::memory_order_acquire); }
void kill() noexcept { kill_switch_.store(true, std::memory_order_release); }
void reset() noexcept { kill_switch_.store(false, std::memory_order_release); }
// Order validation - throws RiskError if invalid
void validate_order(const OrderRequest& request);
// Position tracking
void update_position(const std::string& asset, Decimal quantity, Side side);
[[nodiscard]] Decimal position(const std::string& asset) const;
[[nodiscard]] std::unordered_map<std::string, Decimal> positions() const;
// PnL tracking
void update_pnl(Decimal pnl);
[[nodiscard]] Decimal daily_pnl() const;
void reset_daily_pnl();
// Order tracking
void order_opened(const std::string& symbol);
void order_closed(const std::string& symbol);
[[nodiscard]] int open_orders(const std::string& symbol) const;
// Pre-trade checks (return false instead of throwing)
[[nodiscard]] bool check_order_size(Decimal quantity) const noexcept;
[[nodiscard]] bool check_position_limit(const std::string& asset, Decimal new_position) const noexcept;
[[nodiscard]] bool check_daily_loss() const noexcept;
[[nodiscard]] bool check_open_orders(const std::string& symbol) const noexcept;
private:
RiskConfig config_;
std::atomic<bool> kill_switch_{false};
mutable std::shared_mutex position_mutex_;
std::unordered_map<std::string, Decimal> positions_;
mutable std::shared_mutex pnl_mutex_;
Decimal daily_pnl_;
mutable std::shared_mutex orders_mutex_;
std::unordered_map<std::string, int> open_orders_;
};
// RAII order tracker
class OrderTracker {
public:
OrderTracker(RiskManager& rm, const std::string& symbol)
: risk_manager_(rm), symbol_(symbol) {
risk_manager_.order_opened(symbol_);
}
~OrderTracker() {
if (!released_) {
risk_manager_.order_closed(symbol_);
}
}
void release() { released_ = true; }
private:
RiskManager& risk_manager_;
std::string symbol_;
bool released_ = false;
};
} // namespace lx::trading
@@ -1,417 +0,0 @@
// LX Trading SDK - Core Types
// Zero-copy, cache-friendly structures for HFT
#pragma once
#include <array>
#include <chrono>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
namespace lx::trading {
// Fixed-point decimal for exact financial arithmetic
// Stores value as integer * 10^(-precision)
class Decimal {
public:
static constexpr int PRECISION = 8;
static constexpr int64_t SCALE = 100000000LL;
constexpr Decimal() noexcept : value_(0) {}
constexpr explicit Decimal(int64_t scaled) noexcept : value_(scaled) {}
static Decimal from_double(double d) noexcept {
return Decimal(static_cast<int64_t>(d * SCALE));
}
static Decimal from_string(std::string_view s);
[[nodiscard]] double to_double() const noexcept {
return static_cast<double>(value_) / SCALE;
}
[[nodiscard]] std::string to_string() const;
[[nodiscard]] int64_t scaled_value() const noexcept { return value_; }
constexpr Decimal operator+(Decimal rhs) const noexcept {
return Decimal(value_ + rhs.value_);
}
constexpr Decimal operator-(Decimal rhs) const noexcept {
return Decimal(value_ - rhs.value_);
}
constexpr Decimal operator*(Decimal rhs) const noexcept {
return Decimal((value_ * rhs.value_) / SCALE);
}
constexpr Decimal operator/(Decimal rhs) const noexcept {
return Decimal((value_ * SCALE) / rhs.value_);
}
constexpr bool operator==(Decimal rhs) const noexcept { return value_ == rhs.value_; }
constexpr bool operator!=(Decimal rhs) const noexcept { return value_ != rhs.value_; }
constexpr bool operator<(Decimal rhs) const noexcept { return value_ < rhs.value_; }
constexpr bool operator<=(Decimal rhs) const noexcept { return value_ <= rhs.value_; }
constexpr bool operator>(Decimal rhs) const noexcept { return value_ > rhs.value_; }
constexpr bool operator>=(Decimal rhs) const noexcept { return value_ >= rhs.value_; }
constexpr Decimal abs() const noexcept { return Decimal(value_ < 0 ? -value_ : value_); }
constexpr bool is_zero() const noexcept { return value_ == 0; }
constexpr bool is_positive() const noexcept { return value_ > 0; }
constexpr bool is_negative() const noexcept { return value_ < 0; }
static constexpr Decimal zero() noexcept { return Decimal(0); }
static constexpr Decimal one() noexcept { return Decimal(SCALE); }
private:
int64_t value_;
};
// Trading side
enum class Side : uint8_t {
Buy = 0,
Sell = 1
};
inline constexpr const char* to_string(Side s) noexcept {
return s == Side::Buy ? "buy" : "sell";
}
// Order types
enum class OrderType : uint8_t {
Market = 0,
Limit = 1,
LimitMaker = 2,
StopLoss = 3,
StopLossLimit = 4,
TakeProfit = 5,
TakeProfitLimit = 6
};
inline constexpr const char* to_string(OrderType t) noexcept {
switch (t) {
case OrderType::Market: return "market";
case OrderType::Limit: return "limit";
case OrderType::LimitMaker: return "limit_maker";
case OrderType::StopLoss: return "stop_loss";
case OrderType::StopLossLimit: return "stop_loss_limit";
case OrderType::TakeProfit: return "take_profit";
case OrderType::TakeProfitLimit: return "take_profit_limit";
}
return "unknown";
}
// Time in force
enum class TimeInForce : uint8_t {
GTC = 0, // Good till cancelled
IOC = 1, // Immediate or cancel
FOK = 2, // Fill or kill
GTD = 3, // Good till date
PostOnly = 4
};
inline constexpr const char* to_string(TimeInForce tif) noexcept {
switch (tif) {
case TimeInForce::GTC: return "GTC";
case TimeInForce::IOC: return "IOC";
case TimeInForce::FOK: return "FOK";
case TimeInForce::GTD: return "GTD";
case TimeInForce::PostOnly: return "POST_ONLY";
}
return "unknown";
}
// Order status
enum class OrderStatus : uint8_t {
Pending = 0,
Open = 1,
PartiallyFilled = 2,
Filled = 3,
Cancelled = 4,
Rejected = 5,
Expired = 6
};
inline constexpr const char* to_string(OrderStatus s) noexcept {
switch (s) {
case OrderStatus::Pending: return "pending";
case OrderStatus::Open: return "open";
case OrderStatus::PartiallyFilled: return "partially_filled";
case OrderStatus::Filled: return "filled";
case OrderStatus::Cancelled: return "cancelled";
case OrderStatus::Rejected: return "rejected";
case OrderStatus::Expired: return "expired";
}
return "unknown";
}
// Venue type
enum class VenueType : uint8_t {
Native = 0,
Ccxt = 1,
Hummingbot = 2,
Custom = 3
};
// Trading pair - inline storage for performance
struct TradingPair {
std::array<char, 16> base{};
std::array<char, 16> quote{};
static std::optional<TradingPair> from_symbol(std::string_view symbol);
std::string to_hummingbot() const;
std::string to_ccxt() const;
std::string to_string() const { return to_hummingbot(); }
};
// Fee information
struct Fee {
std::string asset;
Decimal amount;
std::optional<Decimal> rate;
};
// Balance
struct Balance {
std::string asset;
std::string venue;
Decimal free;
Decimal locked;
[[nodiscard]] Decimal total() const noexcept { return free + locked; }
};
// Aggregated balance across venues
struct AggregatedBalance {
std::string asset;
Decimal total_free;
Decimal total_locked;
std::vector<Balance> by_venue;
[[nodiscard]] Decimal total() const noexcept { return total_free + total_locked; }
};
// Price level in orderbook
struct PriceLevel {
Decimal price;
Decimal quantity;
[[nodiscard]] Decimal value() const noexcept { return price * quantity; }
};
// Order request - builder pattern
class OrderRequest {
public:
std::string symbol;
Side side = Side::Buy;
OrderType order_type = OrderType::Market;
Decimal quantity;
std::optional<Decimal> price;
std::optional<Decimal> stop_price;
TimeInForce time_in_force = TimeInForce::GTC;
bool reduce_only = false;
bool post_only = false;
std::optional<std::string> venue;
std::string client_order_id;
OrderRequest() = default;
static OrderRequest market(std::string_view symbol, Side side, Decimal quantity);
static OrderRequest limit(std::string_view symbol, Side side, Decimal quantity, Decimal price);
OrderRequest& with_venue(std::string_view v) {
venue = std::string(v);
return *this;
}
OrderRequest& with_post_only() {
post_only = true;
time_in_force = TimeInForce::PostOnly;
return *this;
}
OrderRequest& with_client_id(std::string_view id) {
client_order_id = std::string(id);
return *this;
}
};
// Order
struct Order {
std::string order_id;
std::string client_order_id;
std::string symbol;
std::string venue;
Side side = Side::Buy;
OrderType order_type = OrderType::Limit;
OrderStatus status = OrderStatus::Pending;
Decimal quantity;
Decimal filled_quantity;
Decimal remaining_quantity;
std::optional<Decimal> price;
std::optional<Decimal> average_price;
int64_t created_at = 0;
int64_t updated_at = 0;
std::vector<Fee> fees;
[[nodiscard]] bool is_open() const noexcept {
return status == OrderStatus::Open ||
status == OrderStatus::PartiallyFilled ||
status == OrderStatus::Pending;
}
[[nodiscard]] bool is_done() const noexcept {
return status == OrderStatus::Filled ||
status == OrderStatus::Cancelled ||
status == OrderStatus::Rejected ||
status == OrderStatus::Expired;
}
[[nodiscard]] Decimal fill_percent() const noexcept {
if (quantity.is_zero()) return Decimal::zero();
return (filled_quantity / quantity) * Decimal::from_double(100.0);
}
};
// Trade/fill
struct Trade {
std::string trade_id;
std::string order_id;
std::string symbol;
std::string venue;
Side side = Side::Buy;
Decimal price;
Decimal quantity;
Fee fee;
int64_t timestamp = 0;
bool is_maker = false;
[[nodiscard]] Decimal value() const noexcept { return price * quantity; }
};
// Ticker
struct Ticker {
std::string symbol;
std::string venue;
std::optional<Decimal> bid;
std::optional<Decimal> ask;
std::optional<Decimal> last;
std::optional<Decimal> volume_24h;
std::optional<Decimal> high_24h;
std::optional<Decimal> low_24h;
std::optional<Decimal> change_24h;
int64_t timestamp = 0;
[[nodiscard]] std::optional<Decimal> mid_price() const noexcept {
if (bid && ask) {
return (*bid + *ask) / Decimal::from_double(2.0);
}
return last;
}
[[nodiscard]] std::optional<Decimal> spread() const noexcept {
if (bid && ask) {
return *ask - *bid;
}
return std::nullopt;
}
[[nodiscard]] std::optional<Decimal> spread_percent() const noexcept {
if (bid && ask && bid->is_positive()) {
return ((*ask - *bid) / *bid) * Decimal::from_double(100.0);
}
return std::nullopt;
}
};
// AMM swap quote
struct SwapQuote {
std::string base_token;
std::string quote_token;
Decimal input_amount;
Decimal output_amount;
Decimal price;
Decimal price_impact;
Decimal fee;
std::vector<std::string> route;
int64_t expires_at = 0;
};
// Pool information
struct PoolInfo {
std::string address;
std::string base_token;
std::string quote_token;
Decimal base_reserve;
Decimal quote_reserve;
Decimal total_liquidity;
Decimal fee_rate;
std::optional<Decimal> apy;
};
// LP position
struct LpPosition {
std::string pool_address;
std::string base_token;
std::string quote_token;
Decimal lp_tokens;
Decimal base_amount;
Decimal quote_amount;
Decimal share_percent;
std::optional<Decimal> unrealized_pnl;
};
// Liquidity operation result
struct LiquidityResult {
std::string tx_hash;
std::string pool_address;
Decimal base_amount;
Decimal quote_amount;
Decimal lp_tokens;
Decimal share_percent;
};
// Venue information
struct VenueInfo {
std::string name;
VenueType venue_type = VenueType::Native;
bool connected = false;
std::optional<int> latency_ms;
std::vector<std::string> supported_pairs;
Decimal maker_fee;
Decimal taker_fee;
};
// Market information
struct MarketInfo {
std::string symbol;
std::string base;
std::string quote;
int price_precision = 8;
int quantity_precision = 8;
Decimal min_quantity;
std::optional<Decimal> max_quantity;
std::optional<Decimal> min_notional;
Decimal tick_size;
Decimal lot_size;
};
// Timestamp utilities
inline int64_t now_ms() noexcept {
using namespace std::chrono;
return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
}
inline int64_t now_us() noexcept {
using namespace std::chrono;
return duration_cast<microseconds>(system_clock::now().time_since_epoch()).count();
}
inline int64_t now_ns() noexcept {
using namespace std::chrono;
return duration_cast<nanoseconds>(system_clock::now().time_since_epoch()).count();
}
} // namespace lx::trading
-10
View File
@@ -1,10 +0,0 @@
// LX Trading SDK - Adapter Base Implementation
#include <lx/trading/adapter.hpp>
namespace lx::trading {
// Base adapter has no implementation - pure interface
// This file exists for potential future common functionality
} // namespace lx::trading
@@ -1,160 +0,0 @@
// LX Trading SDK - Cross-Chain Router Implementation
#include <lx/trading/arbitrage/cross_chain.hpp>
#include <algorithm>
namespace lx::trading::arbitrage {
CrossChainRouter::CrossChainRouter(CrossChainConfig config)
: config_(std::move(config)) {}
void CrossChainRouter::set_warp_client(std::shared_ptr<WarpClient> client) {
std::lock_guard<std::mutex> lock(warp_mutex_);
warp_client_ = std::move(client);
}
void CrossChainRouter::set_teleport_client(std::shared_ptr<TeleportClient> client) {
std::lock_guard<std::mutex> lock(teleport_mutex_);
teleport_client_ = std::move(client);
}
std::shared_ptr<WarpClient> CrossChainRouter::warp() const {
std::lock_guard<std::mutex> lock(warp_mutex_);
return warp_client_;
}
std::shared_ptr<TeleportClient> CrossChainRouter::teleport() const {
std::lock_guard<std::mutex> lock(teleport_mutex_);
return teleport_client_;
}
CrossChainTransport CrossChainRouter::determine_transport(
const std::string& source_chain,
const std::string& dest_chain) const {
auto src_it = config_.chains.find(source_chain);
auto dst_it = config_.chains.find(dest_chain);
// Same chain = direct
if (source_chain == dest_chain) {
return CrossChainTransport::Direct;
}
// CEX = API
if (src_it != config_.chains.end() && src_it->second.chain_type == ChainType::Cex) {
return CrossChainTransport::CexApi;
}
if (dst_it != config_.chains.end() && dst_it->second.chain_type == ChainType::Cex) {
return CrossChainTransport::CexApi;
}
// Both Lux subnets = Warp (fastest)
if (src_it != config_.chains.end() && dst_it != config_.chains.end()) {
const auto& src = src_it->second;
const auto& dst = dst_it->second;
if (src.chain_type == ChainType::LuxSubnet &&
dst.chain_type == ChainType::LuxSubnet &&
src.warp_supported && dst.warp_supported &&
config_.warp_enabled) {
return CrossChainTransport::Warp;
}
}
// Both EVM or mixed = Teleport
if (src_it != config_.chains.end() && dst_it != config_.chains.end()) {
const auto& src = src_it->second;
const auto& dst = dst_it->second;
if (src.teleport_supported && dst.teleport_supported && config_.teleport_enabled) {
return CrossChainTransport::Teleport;
}
}
// No viable transport - return Direct as fallback
return CrossChainTransport::Direct;
}
int64_t CrossChainRouter::estimate_latency(
const std::string& source_chain,
const std::string& dest_chain) const {
CrossChainTransport transport = determine_transport(source_chain, dest_chain);
switch (transport) {
case CrossChainTransport::Direct:
return 0;
case CrossChainTransport::Warp:
return 500; // Sub-second
case CrossChainTransport::CexApi:
return 100; // API call
case CrossChainTransport::Teleport: {
auto src_it = config_.chains.find(source_chain);
if (src_it != config_.chains.end()) {
return static_cast<int64_t>(src_it->second.finality_ms) + 10000; // Finality + processing
}
return 3600000; // 1 hour fallback
}
}
return 3600000;
}
Decimal CrossChainRouter::estimate_cost(
const std::string& source_chain,
const std::string& dest_chain,
const std::string& token,
Decimal amount) const {
CrossChainTransport transport = determine_transport(source_chain, dest_chain);
switch (transport) {
case CrossChainTransport::Direct:
return Decimal::zero();
case CrossChainTransport::Warp:
return Decimal::from_double(0.001); // Nearly free
case CrossChainTransport::CexApi:
return Decimal::zero(); // No bridge cost
case CrossChainTransport::Teleport: {
std::lock_guard<std::mutex> lock(teleport_mutex_);
if (teleport_client_) {
try {
return teleport_client_->estimate_bridge_fee(dest_chain, token, amount);
} catch (...) {
return Decimal::from_double(1.0); // Fallback estimate
}
}
return Decimal::from_double(1.0); // Estimate $1
}
}
return Decimal::zero();
}
std::string CrossChainRouter::venue_to_chain(const std::string& venue) const {
for (const auto& [chain_id, info] : config_.chains) {
for (const auto& v : info.venues) {
if (v == venue) {
return chain_id;
}
}
}
return venue; // Fallback to venue name
}
EnhancedOpportunity CrossChainRouter::enhance_opportunity(const UnifiedOpportunity& opp) const {
std::string buy_chain = venue_to_chain(opp.buy_venue);
std::string sell_chain = venue_to_chain(opp.sell_venue);
CrossChainTransport transport = determine_transport(buy_chain, sell_chain);
int64_t estimated_latency = estimate_latency(buy_chain, sell_chain);
Decimal bridge_cost = estimate_cost(buy_chain, sell_chain, opp.symbol, opp.max_size);
return EnhancedOpportunity{
.base = opp,
.transport = transport,
.estimated_latency = estimated_latency,
.bridge_cost = bridge_cost,
.adjusted_net_profit = opp.net_profit - bridge_cost
};
}
} // namespace lx::trading::arbitrage
@@ -1,172 +0,0 @@
// LX Trading SDK - LX-First Arbitrage Implementation
#include <lx/trading/arbitrage/lx_first.hpp>
#include <algorithm>
#include <cmath>
namespace lx::trading::arbitrage {
LxFirstArbitrage::LxFirstArbitrage(LxFirstConfig config)
: config_(std::move(config)) {}
void LxFirstArbitrage::update_lx_price(const LxPrice& price) {
std::string symbol = price.symbol;
{
std::lock_guard<std::mutex> lock(lx_prices_mutex_);
lx_prices_[symbol] = price;
}
// Immediately check for opportunities against stale venues
check_opportunities(symbol);
}
void LxFirstArbitrage::update_venue_price(const VenuePrice& price) {
std::lock_guard<std::mutex> lock(venue_prices_mutex_);
auto& prices = venue_prices_[price.symbol];
// Update or append
bool found = false;
for (auto& p : prices) {
if (p.venue == price.venue) {
p = price;
found = true;
break;
}
}
if (!found) {
prices.push_back(price);
}
}
void LxFirstArbitrage::on_opportunity(LxFirstCallback callback) {
std::lock_guard<std::mutex> lock(callbacks_mutex_);
callbacks_.push_back(std::move(callback));
}
void LxFirstArbitrage::start() {
running_.store(true);
}
void LxFirstArbitrage::stop() {
running_.store(false);
}
void LxFirstArbitrage::check_opportunities(const std::string& symbol) {
if (!running_.load()) {
return;
}
LxPrice lx_price;
{
std::lock_guard<std::mutex> lock(lx_prices_mutex_);
auto it = lx_prices_.find(symbol);
if (it == lx_prices_.end()) {
return;
}
lx_price = it->second;
}
std::vector<VenuePrice> vps;
{
std::lock_guard<std::mutex> lock(venue_prices_mutex_);
auto it = venue_prices_.find(symbol);
if (it == venue_prices_.end()) {
return;
}
vps = it->second;
}
int64_t now = now_ms();
for (const auto& vp : vps) {
// Calculate how stale the venue is
int64_t staleness = now - vp.timestamp;
if (staleness > config_.max_staleness_ms) {
continue; // Too stale, might have updated by now
}
// Check for BUY opportunity (venue ask < LX mid)
// The slow venue hasn't caught up to LX's higher price
if (vp.ask < lx_price.mid) {
Decimal divergence = lx_price.mid - vp.ask;
Decimal divergence_bps = (divergence / lx_price.mid) * Decimal::from_double(10000.0);
if (divergence_bps >= config_.min_divergence_bps) {
auto opp = create_opportunity(
symbol, lx_price, vp, staleness,
"buy", divergence, divergence_bps
);
if (opp.expected_profit >= config_.min_profit) {
std::lock_guard<std::mutex> lock(callbacks_mutex_);
for (const auto& callback : callbacks_) {
callback(opp);
}
}
}
}
// Check for SELL opportunity (venue bid > LX mid)
// The slow venue hasn't caught up to LX's lower price
if (vp.bid > lx_price.mid) {
Decimal divergence = vp.bid - lx_price.mid;
Decimal divergence_bps = (divergence / lx_price.mid) * Decimal::from_double(10000.0);
if (divergence_bps >= config_.min_divergence_bps) {
auto opp = create_opportunity(
symbol, lx_price, vp, staleness,
"sell", divergence, divergence_bps
);
if (opp.expected_profit >= config_.min_profit) {
std::lock_guard<std::mutex> lock(callbacks_mutex_);
for (const auto& callback : callbacks_) {
callback(opp);
}
}
}
}
}
}
LxFirstOpportunity LxFirstArbitrage::create_opportunity(
const std::string& symbol,
const LxPrice& lx_price,
const VenuePrice& vp,
int64_t staleness,
const std::string& side,
Decimal divergence,
Decimal divergence_bps) const {
int64_t now = now_ms();
Decimal expected_profit = divergence * config_.max_position_size;
double confidence = calculate_confidence(staleness, divergence_bps);
LxFirstOpportunity opp;
opp.id = symbol + "-" + vp.venue + "-" + side + "-" + std::to_string(now);
opp.symbol = symbol;
opp.timestamp = now;
opp.lx_price = lx_price;
opp.stale_venue = vp.venue;
opp.stale_price = vp;
opp.staleness = staleness;
opp.side = side;
opp.divergence = divergence;
opp.divergence_bps = divergence_bps;
opp.expected_profit = expected_profit;
opp.max_size = config_.max_position_size;
opp.confidence = confidence;
return opp;
}
double LxFirstArbitrage::calculate_confidence(int64_t staleness, Decimal divergence_bps) const {
// Higher confidence when:
// 1. Venue is more stale (hasn't had time to update)
// 2. Divergence is larger (more room for profit)
double staleness_score = std::max(0.0, 1.0 - static_cast<double>(staleness) / 5000.0); // 5s max
double divergence_score = std::min(1.0, divergence_bps.to_double() / 100.0); // 100bps = 1.0
return 0.5 * staleness_score + 0.5 * divergence_score;
}
} // namespace lx::trading::arbitrage
@@ -1,366 +0,0 @@
// LX Trading SDK - Arbitrage Scanner Implementation
#include <lx/trading/arbitrage/scanner.hpp>
#include <algorithm>
#include <chrono>
namespace lx::trading::arbitrage {
Scanner::Scanner(ScannerConfig config)
: config_(std::move(config)) {}
Scanner::~Scanner() {
stop();
}
void Scanner::add_chain(const CrossChainInfo& info) {
std::lock_guard<std::mutex> lock(chains_mutex_);
chains_[info.chain_id] = info;
}
void Scanner::update_price(const PriceSource& source) {
std::lock_guard<std::mutex> lock(prices_mutex_);
auto& sources = prices_[source.symbol];
// Update existing or append new
bool found = false;
for (auto& s : sources) {
if (s.chain_id == source.chain_id && s.venue == source.venue) {
s = source;
found = true;
break;
}
}
if (!found) {
sources.push_back(source);
}
}
void Scanner::on_opportunity(OpportunityCallback callback) {
std::lock_guard<std::mutex> lock(callbacks_mutex_);
callbacks_.push_back(std::move(callback));
}
void Scanner::start() {
if (running_.exchange(true)) {
return; // Already running
}
scan_thread_ = std::make_unique<std::thread>(&Scanner::scan_loop, this);
}
void Scanner::stop() {
running_.store(false);
if (scan_thread_ && scan_thread_->joinable()) {
scan_thread_->join();
}
scan_thread_.reset();
}
void Scanner::scan_loop() {
while (running_.load()) {
auto opportunities = scan();
// Emit opportunities
{
std::lock_guard<std::mutex> lock(callbacks_mutex_);
for (const auto& opp : opportunities) {
for (const auto& callback : callbacks_) {
callback(opp);
}
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(config_.scan_interval_ms));
}
}
std::vector<ArbitrageOpportunity> Scanner::scan() {
std::vector<ArbitrageOpportunity> opportunities;
std::lock_guard<std::mutex> lock(prices_mutex_);
for (const auto& [symbol, sources] : prices_) {
if (sources.size() < 2) {
continue;
}
int64_t now = now_ms();
// Filter stale prices
std::vector<PriceSource> valid_sources;
for (const auto& s : sources) {
if (now - s.timestamp < config_.max_price_age_ms) {
valid_sources.push_back(s);
}
}
if (valid_sources.size() < 2) {
continue;
}
// Simple arbitrage
auto simple_opps = find_simple_arb(symbol, valid_sources);
opportunities.insert(opportunities.end(), simple_opps.begin(), simple_opps.end());
// CEX-DEX arbitrage
auto cex_dex_opps = find_cex_dex_arb(symbol, valid_sources);
opportunities.insert(opportunities.end(), cex_dex_opps.begin(), cex_dex_opps.end());
}
return opportunities;
}
std::vector<ArbitrageOpportunity> Scanner::find_simple_arb(
const std::string& symbol,
const std::vector<PriceSource>& sources) const {
std::vector<ArbitrageOpportunity> opportunities;
// Sort by ask (lowest first for buying)
std::vector<PriceSource> buy_order = sources;
std::sort(buy_order.begin(), buy_order.end(),
[](const auto& a, const auto& b) { return a.ask < b.ask; });
// Sort by bid (highest first for selling)
std::vector<PriceSource> sell_order = sources;
std::sort(sell_order.begin(), sell_order.end(),
[](const auto& a, const auto& b) { return a.bid > b.bid; });
for (const auto& buy_src : buy_order) {
for (const auto& sell_src : sell_order) {
// Skip same venue/chain
if (buy_src.chain_id == sell_src.chain_id && buy_src.venue == sell_src.venue) {
continue;
}
// Calculate spread
Decimal spread = sell_src.bid - buy_src.ask;
if (spread <= Decimal::zero()) {
continue;
}
Decimal spread_bps = (spread / buy_src.ask) * Decimal::from_double(10000.0);
if (spread_bps < config_.min_spread_bps) {
continue;
}
// Calculate costs
auto [gas_cost, bridge_cost] = calculate_costs(buy_src.chain_id, sell_src.chain_id);
// Maximum size limited by liquidity
Decimal max_size = std::min(buy_src.liquidity, sell_src.liquidity);
// Calculate PnL
Decimal gross_pnl = spread * max_size;
Decimal net_pnl = gross_pnl - gas_cost - bridge_cost;
if (net_pnl < config_.min_profit_usd) {
continue;
}
// Calculate confidence
double confidence = calculate_confidence(buy_src, sell_src);
int64_t now = now_ms();
ArbitrageOpportunity opp;
opp.id = "simple-" + symbol + "-" + buy_src.venue + "-" + sell_src.venue + "-" + std::to_string(now);
opp.arb_type = ArbType::Simple;
opp.buy_source = buy_src;
opp.sell_source = sell_src;
opp.spread_bps = spread_bps;
opp.estimated_pnl = gross_pnl;
opp.max_size = max_size;
opp.gas_cost_usd = gas_cost;
opp.bridge_cost_usd = bridge_cost;
opp.net_pnl = net_pnl;
opp.confidence = confidence;
opp.expires_at = now + 5000;
// Build routes
opp.routes.push_back(Route{
.chain_id = buy_src.chain_id,
.venue = buy_src.venue,
.action = "buy",
.token_in = "USDC",
.token_out = symbol,
.amount_in = max_size * buy_src.ask,
.expected_out = max_size,
.min_amount_out = max_size * Decimal::from_double(0.99)
});
opp.routes.push_back(Route{
.chain_id = sell_src.chain_id,
.venue = sell_src.venue,
.action = "sell",
.token_in = symbol,
.token_out = "USDC",
.amount_in = max_size,
.expected_out = max_size * sell_src.bid,
.min_amount_out = max_size * sell_src.bid * Decimal::from_double(0.99)
});
opportunities.push_back(std::move(opp));
}
}
return opportunities;
}
std::vector<ArbitrageOpportunity> Scanner::find_cex_dex_arb(
const std::string& symbol,
const std::vector<PriceSource>& sources) const {
std::vector<ArbitrageOpportunity> opportunities;
// Separate CEX and DEX sources
std::vector<PriceSource> cex_sources;
std::vector<PriceSource> dex_sources;
for (const auto& s : sources) {
if (CEX_VENUES.count(s.venue)) {
cex_sources.push_back(s);
} else {
dex_sources.push_back(s);
}
}
// CEX buy -> DEX sell
for (const auto& cex : cex_sources) {
for (const auto& dex : dex_sources) {
Decimal spread = dex.bid - cex.ask;
if (spread <= Decimal::zero()) {
continue;
}
Decimal spread_bps = (spread / cex.ask) * Decimal::from_double(10000.0);
if (spread_bps < config_.min_spread_bps) {
continue;
}
Decimal max_size = std::min(cex.liquidity, dex.liquidity);
Decimal gross_pnl = spread * max_size;
int64_t now = now_ms();
ArbitrageOpportunity opp;
opp.id = "cexdex-" + symbol + "-" + cex.venue + "-" + dex.venue + "-" + std::to_string(now);
opp.arb_type = ArbType::CexDex;
opp.buy_source = cex;
opp.sell_source = dex;
opp.spread_bps = spread_bps;
opp.estimated_pnl = gross_pnl;
opp.max_size = max_size;
opp.gas_cost_usd = Decimal::from_double(0.5);
opp.bridge_cost_usd = Decimal::zero();
opp.net_pnl = gross_pnl - Decimal::from_double(0.5);
opp.confidence = 0.7;
opp.expires_at = now + 3000;
opportunities.push_back(std::move(opp));
}
}
// DEX buy -> CEX sell
for (const auto& dex : dex_sources) {
for (const auto& cex : cex_sources) {
Decimal spread = cex.bid - dex.ask;
if (spread <= Decimal::zero()) {
continue;
}
Decimal spread_bps = (spread / dex.ask) * Decimal::from_double(10000.0);
if (spread_bps < config_.min_spread_bps) {
continue;
}
Decimal max_size = std::min(dex.liquidity, cex.liquidity);
Decimal gross_pnl = spread * max_size;
int64_t now = now_ms();
ArbitrageOpportunity opp;
opp.id = "cexdex-" + symbol + "-" + dex.venue + "-" + cex.venue + "-" + std::to_string(now);
opp.arb_type = ArbType::CexDex;
opp.buy_source = dex;
opp.sell_source = cex;
opp.spread_bps = spread_bps;
opp.estimated_pnl = gross_pnl;
opp.max_size = max_size;
opp.gas_cost_usd = Decimal::from_double(0.5);
opp.bridge_cost_usd = Decimal::zero();
opp.net_pnl = gross_pnl - Decimal::from_double(0.5);
opp.confidence = 0.7;
opp.expires_at = now + 3000;
opportunities.push_back(std::move(opp));
}
}
return opportunities;
}
std::pair<Decimal, Decimal> Scanner::calculate_costs(
const std::string& source_chain,
const std::string& dest_chain) const {
std::lock_guard<std::mutex> lock(chains_mutex_);
auto src_it = chains_.find(source_chain);
auto dst_it = chains_.find(dest_chain);
// Estimate gas cost
Decimal gas_cost = src_it != chains_.end()
? Decimal::from_double(0.05)
: Decimal::from_double(0.1);
// Bridge cost if crossing chains
Decimal bridge_cost = Decimal::zero();
if (source_chain != dest_chain && src_it != chains_.end() && dst_it != chains_.end()) {
const auto& src = src_it->second;
const auto& dst = dst_it->second;
if (src.warp_supported && dst.warp_supported) {
bridge_cost = Decimal::from_double(0.01); // Warp is nearly free
} else if (src.teleport_supported && dst.teleport_supported) {
bridge_cost = Decimal::from_double(0.10); // Teleport for EVM
} else {
bridge_cost = Decimal::from_double(1.0); // Generic bridge
}
}
return {gas_cost, bridge_cost};
}
double Scanner::calculate_confidence(
const PriceSource& buy,
const PriceSource& sell) const {
int64_t now = now_ms();
double max_age = static_cast<double>(config_.max_price_age_ms) / 1000.0;
// Freshness score
double buy_age = static_cast<double>(now - buy.timestamp) / 1000.0;
double sell_age = static_cast<double>(now - sell.timestamp) / 1000.0;
double freshness_score = std::max(0.0, 1.0 - (buy_age + sell_age) / (2.0 * max_age));
// Liquidity score
Decimal min_liq = std::min(buy.liquidity, sell.liquidity);
double liquidity_score;
if (min_liq > Decimal::from_double(100000.0)) {
liquidity_score = 1.0;
} else if (min_liq > Decimal::from_double(10000.0)) {
liquidity_score = 0.8;
} else {
liquidity_score = 0.5;
}
// Latency score
double avg_latency = static_cast<double>(buy.latency + sell.latency) / 2.0;
double latency_score = std::max(0.0, 1.0 - avg_latency / 1000.0);
// Weighted average
return 0.4 * freshness_score + 0.4 * liquidity_score + 0.2 * latency_score;
}
} // namespace lx::trading::arbitrage
@@ -1,93 +0,0 @@
// LX Trading SDK - Arbitrage Types Implementation
#include <lx/trading/arbitrage/types.hpp>
namespace lx::trading::arbitrage {
CrossChainConfig CrossChainConfig::defaults() {
CrossChainConfig config;
// Lux ecosystem (Warp enabled)
config.chains["lux_mainnet"] = CrossChainInfo{
.chain_id = "lux_mainnet",
.name = "Lux Mainnet",
.chain_type = ChainType::LuxSubnet,
.block_time_ms = 400,
.finality_ms = 400,
.warp_supported = true,
.teleport_supported = true,
.venues = {"lx_dex", "lx_amm"}
};
config.chains["lx_dex_subnet"] = CrossChainInfo{
.chain_id = "lx_dex_subnet",
.name = "LX DEX Subnet",
.chain_type = ChainType::LuxSubnet,
.block_time_ms = 200,
.finality_ms = 200,
.warp_supported = true,
.teleport_supported = false,
.venues = {"lx_dex"}
};
// EVM chains (Teleport enabled)
config.chains["ethereum"] = CrossChainInfo{
.chain_id = "1",
.name = "Ethereum",
.chain_type = ChainType::Evm,
.block_time_ms = 12000,
.finality_ms = 15 * 60 * 1000, // 15 minutes
.warp_supported = false,
.teleport_supported = true,
.venues = {"uniswap", "sushiswap"}
};
config.chains["bsc"] = CrossChainInfo{
.chain_id = "56",
.name = "BNB Smart Chain",
.chain_type = ChainType::Evm,
.block_time_ms = 3000,
.finality_ms = 45000,
.warp_supported = false,
.teleport_supported = true,
.venues = {"pancakeswap"}
};
config.chains["arbitrum"] = CrossChainInfo{
.chain_id = "42161",
.name = "Arbitrum One",
.chain_type = ChainType::Evm,
.block_time_ms = 250,
.finality_ms = 15 * 60 * 1000,
.warp_supported = false,
.teleport_supported = true,
.venues = {"uniswap", "camelot"}
};
// CEX (API only)
config.chains["binance"] = CrossChainInfo{
.chain_id = "binance",
.name = "Binance",
.chain_type = ChainType::Cex,
.block_time_ms = 0,
.finality_ms = 0,
.warp_supported = false,
.teleport_supported = false,
.venues = {"binance"}
};
config.chains["mexc"] = CrossChainInfo{
.chain_id = "mexc",
.name = "MEXC",
.chain_type = ChainType::Cex,
.block_time_ms = 0,
.finality_ms = 0,
.warp_supported = false,
.teleport_supported = false,
.venues = {"mexc"}
};
return config;
}
} // namespace lx::trading::arbitrage
@@ -1,263 +0,0 @@
// LX Trading SDK - Unified Arbitrage Implementation
#include <lx/trading/arbitrage/unified.hpp>
#include <algorithm>
#include <chrono>
namespace lx::trading::arbitrage {
UnifiedArbitrage::UnifiedArbitrage(std::shared_ptr<TradingClient> client, UnifiedArbConfig config)
: client_(std::move(client)),
config_(std::move(config)),
total_pnl_(Decimal::zero()) {}
UnifiedArbitrage::~UnifiedArbitrage() {
stop();
}
void UnifiedArbitrage::start() {
if (running_.exchange(true)) {
return; // Already running
}
scan_thread_ = std::make_unique<std::thread>(&UnifiedArbitrage::scan_loop, this);
execute_thread_ = std::make_unique<std::thread>(&UnifiedArbitrage::execute_loop, this);
}
void UnifiedArbitrage::stop() {
running_.store(false);
if (scan_thread_ && scan_thread_->joinable()) {
scan_thread_->join();
}
scan_thread_.reset();
if (execute_thread_ && execute_thread_->joinable()) {
execute_thread_->join();
}
execute_thread_.reset();
}
void UnifiedArbitrage::on_opportunity(UnifiedCallback callback) {
std::lock_guard<std::mutex> lock(callbacks_mutex_);
callbacks_.push_back(std::move(callback));
}
UnifiedArbStats UnifiedArbitrage::get_stats() const {
std::lock_guard<std::mutex> pnl_lock(pnl_mutex_);
std::lock_guard<std::mutex> exec_lock(executions_mutex_);
uint64_t successful = 0;
for (const auto& exec : executions_) {
if (exec.status == "completed" && exec.actual_profit.is_positive()) {
++successful;
}
}
double win_rate = executions_.empty()
? 0.0
: static_cast<double>(successful) / static_cast<double>(executions_.size());
return UnifiedArbStats{
.total_executions = executions_.size(),
.successful_executions = successful,
.total_pnl = total_pnl_,
.win_rate = win_rate
};
}
void UnifiedArbitrage::scan_loop() {
while (running_.load()) {
// Scan each symbol
for (const auto& symbol : config_.symbols) {
auto opp = find_opportunity(symbol);
if (opp && opp->net_profit > config_.min_profit) {
// Add to queue
{
std::lock_guard<std::mutex> lock(queue_mutex_);
if (opportunity_queue_.size() < 1000) {
opportunity_queue_.push_back(*opp);
}
}
// Emit to callbacks
{
std::lock_guard<std::mutex> lock(callbacks_mutex_);
for (const auto& callback : callbacks_) {
callback(*opp);
}
}
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(config_.scan_interval_ms));
}
}
std::optional<UnifiedOpportunity> UnifiedArbitrage::find_opportunity(const std::string& symbol) {
AggregatedBook book;
try {
book = client_->aggregated_orderbook(symbol);
} catch (...) {
return std::nullopt;
}
if (book.bids.empty() || book.asks.empty()) {
return std::nullopt;
}
const auto& best_bid = book.bids[0];
const auto& best_ask = book.asks[0];
// Cross-venue arbitrage: bid on one venue > ask on another
if (best_bid.price <= best_ask.price) {
return std::nullopt;
}
Decimal spread = best_bid.price - best_ask.price;
Decimal spread_bps = (spread / best_ask.price) * Decimal::from_double(10000.0);
if (spread_bps < config_.min_spread_bps) {
return std::nullopt;
}
Decimal max_size = std::min({
best_bid.quantity,
best_ask.quantity,
config_.max_position_size
});
Decimal gross_profit = spread * max_size;
Decimal total_fees = best_ask.price * max_size * Decimal::from_double(0.002); // ~0.2% total fees
Decimal net_profit = gross_profit - total_fees;
int64_t now = now_ms();
UnifiedOpportunity opp;
opp.id = "arb-" + symbol + "-" + std::to_string(now);
opp.symbol = symbol;
opp.timestamp = now;
opp.expires_at = now + 5000;
opp.buy_venue = best_ask.venue;
opp.buy_price = best_ask.price;
opp.buy_size = best_ask.quantity;
opp.sell_venue = best_bid.venue;
opp.sell_price = best_bid.price;
opp.sell_size = best_bid.quantity;
opp.spread = spread;
opp.spread_bps = spread_bps;
opp.max_size = max_size;
opp.gross_profit = gross_profit;
opp.est_fees = total_fees;
opp.net_profit = net_profit;
opp.confidence = 0.8;
opp.latency = now - best_ask.timestamp;
return opp;
}
void UnifiedArbitrage::execute_loop() {
while (running_.load()) {
UnifiedOpportunity opp;
bool has_opp = false;
{
std::lock_guard<std::mutex> lock(queue_mutex_);
if (!opportunity_queue_.empty()) {
opp = opportunity_queue_.front();
opportunity_queue_.pop_front();
has_opp = true;
}
}
if (has_opp) {
auto result = execute_opportunity(opp);
// Update stats
{
std::lock_guard<std::mutex> lock(pnl_mutex_);
total_pnl_ = total_pnl_ + result.actual_profit;
}
{
std::lock_guard<std::mutex> lock(executions_mutex_);
executions_.push_back(std::move(result));
}
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
}
UnifiedExecution UnifiedArbitrage::execute_opportunity(const UnifiedOpportunity& opp) {
int64_t now = now_ms();
// Check if expired
if (now > opp.expires_at) {
return UnifiedExecution{
.id = opp.id,
.opportunity = opp,
.start_time = now,
.end_time = now,
.status = "expired",
.buy_order_id = std::nullopt,
.sell_order_id = std::nullopt,
.actual_profit = Decimal::zero(),
.fees = Decimal::zero(),
.error = "Opportunity expired"
};
}
UnifiedExecution exec_result;
exec_result.id = opp.id;
exec_result.opportunity = opp;
exec_result.start_time = now;
exec_result.status = "executing";
exec_result.actual_profit = Decimal::zero();
exec_result.fees = Decimal::zero();
try {
// Execute both legs
OrderRequest buy_request = OrderRequest::limit(opp.symbol, Side::Buy, opp.max_size, opp.buy_price)
.with_venue(opp.buy_venue);
OrderRequest sell_request = OrderRequest::limit(opp.symbol, Side::Sell, opp.max_size, opp.sell_price)
.with_venue(opp.sell_venue);
// Execute orders (in production, these would be async/parallel)
Order buy_order = client_->place_order(buy_request);
Order sell_order = client_->place_order(sell_request);
exec_result.end_time = now_ms();
exec_result.buy_order_id = buy_order.order_id;
exec_result.sell_order_id = sell_order.order_id;
// Calculate actual profit
if (buy_order.average_price && sell_order.average_price) {
Decimal buy_value = *buy_order.average_price * buy_order.filled_quantity;
Decimal sell_value = *sell_order.average_price * sell_order.filled_quantity;
exec_result.actual_profit = sell_value - buy_value;
// Subtract fees
Decimal buy_fees = Decimal::zero();
for (const auto& f : buy_order.fees) {
buy_fees = buy_fees + f.amount;
}
Decimal sell_fees = Decimal::zero();
for (const auto& f : sell_order.fees) {
sell_fees = sell_fees + f.amount;
}
exec_result.fees = buy_fees + sell_fees;
exec_result.actual_profit = exec_result.actual_profit - exec_result.fees;
}
exec_result.status = "completed";
} catch (const std::exception& e) {
exec_result.end_time = now_ms();
exec_result.status = "failed";
exec_result.error = e.what();
}
return exec_result;
}
} // namespace lx::trading::arbitrage
-432
View File
@@ -1,432 +0,0 @@
// LX Trading SDK - CCXT Adapter Implementation
#include <lx/trading/adapters/ccxt.hpp>
#include <lx/trading/orderbook.hpp>
#include <cpr/cpr.h>
#include <nlohmann/json.hpp>
namespace lx::trading {
using json = nlohmann::json;
CcxtAdapter::CcxtAdapter(std::string_view name, const CcxtConfig& config)
: name_(name), config_(config), capabilities_(VenueCapabilities::clob()) {
capabilities_.batch_orders = false; // CCXT REST doesn't have unified batch
}
CcxtAdapter::~CcxtAdapter() = default;
void CcxtAdapter::set_service_url(std::string_view url) {
service_url_ = std::string(url);
}
void CcxtAdapter::update_latency(int64_t start_ns) {
int64_t elapsed = now_ns() - start_ns;
latency_.store(static_cast<int>(elapsed / 1000000), std::memory_order_release);
}
std::future<void> CcxtAdapter::connect() {
return std::async(std::launch::async, [this]() {
auto start = now_ns();
json body = {
{"exchange", config_.exchange_id},
{"apiKey", config_.api_key.value_or("")},
{"secret", config_.api_secret.value_or("")},
{"sandbox", config_.sandbox}
};
if (config_.password) {
body["password"] = *config_.password;
}
auto response = cpr::Post(
cpr::Url{service_url_ + "/connect"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{body.dump()});
if (response.status_code != 200) {
throw AdapterError("CCXT connect failed: " + response.text);
}
update_latency(start);
connected_.store(true, std::memory_order_release);
});
}
std::future<void> CcxtAdapter::disconnect() {
return std::async(std::launch::async, [this]() {
connected_.store(false, std::memory_order_release);
});
}
std::future<std::vector<MarketInfo>> CcxtAdapter::get_markets() {
return std::async(std::launch::async, [this]() {
auto start = now_ns();
auto response = cpr::Get(
cpr::Url{service_url_ + "/markets/" + config_.exchange_id});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to get markets: " + response.text);
}
auto data = json::parse(response.text);
std::vector<MarketInfo> markets;
for (const auto& m : data) {
MarketInfo info;
info.symbol = m["symbol"];
info.base = m.value("base", "");
info.quote = m.value("quote", "");
info.price_precision = m.value("precision", json{}).value("price", 8);
info.quantity_precision = m.value("precision", json{}).value("amount", 8);
auto limits = m.value("limits", json{});
auto amount_limits = limits.value("amount", json{});
if (amount_limits.contains("min") && !amount_limits["min"].is_null()) {
info.min_quantity = Decimal::from_double(amount_limits["min"].get<double>());
}
if (amount_limits.contains("max") && !amount_limits["max"].is_null()) {
info.max_quantity = Decimal::from_double(amount_limits["max"].get<double>());
}
info.tick_size = Decimal::from_double(0.00000001);
info.lot_size = Decimal::from_double(0.00000001);
markets.push_back(std::move(info));
}
return markets;
});
}
std::future<Ticker> CcxtAdapter::get_ticker(const std::string& symbol) {
return std::async(std::launch::async, [this, symbol]() {
auto start = now_ns();
auto response = cpr::Get(
cpr::Url{service_url_ + "/ticker/" + config_.exchange_id + "/" + symbol});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to get ticker: " + response.text);
}
auto data = json::parse(response.text);
Ticker ticker;
ticker.symbol = data.value("symbol", symbol);
ticker.venue = name_;
if (data.contains("bid") && !data["bid"].is_null())
ticker.bid = Decimal::from_double(data["bid"].get<double>());
if (data.contains("ask") && !data["ask"].is_null())
ticker.ask = Decimal::from_double(data["ask"].get<double>());
if (data.contains("last") && !data["last"].is_null())
ticker.last = Decimal::from_double(data["last"].get<double>());
if (data.contains("baseVolume") && !data["baseVolume"].is_null())
ticker.volume_24h = Decimal::from_double(data["baseVolume"].get<double>());
if (data.contains("high") && !data["high"].is_null())
ticker.high_24h = Decimal::from_double(data["high"].get<double>());
if (data.contains("low") && !data["low"].is_null())
ticker.low_24h = Decimal::from_double(data["low"].get<double>());
if (data.contains("percentage") && !data["percentage"].is_null())
ticker.change_24h = Decimal::from_double(data["percentage"].get<double>());
ticker.timestamp = data.value("timestamp", now_ms());
return ticker;
});
}
std::future<std::unique_ptr<Orderbook>> CcxtAdapter::get_orderbook(
const std::string& symbol, std::optional<int> depth) {
return std::async(std::launch::async, [this, symbol, depth]() {
auto start = now_ns();
std::string url = service_url_ + "/orderbook/" + config_.exchange_id + "/" + symbol;
if (depth) url += "?limit=" + std::to_string(*depth);
auto response = cpr::Get(cpr::Url{url});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to get orderbook: " + response.text);
}
auto data = json::parse(response.text);
auto book = std::make_unique<Orderbook>(symbol, name_);
for (const auto& bid : data["bids"]) {
book->add_bid(
Decimal::from_double(bid[0].get<double>()),
Decimal::from_double(bid[1].get<double>()));
}
for (const auto& ask : data["asks"]) {
book->add_ask(
Decimal::from_double(ask[0].get<double>()),
Decimal::from_double(ask[1].get<double>()));
}
book->sort();
return book;
});
}
std::future<std::vector<Trade>> CcxtAdapter::get_trades(
const std::string& symbol, std::optional<int> limit) {
return std::async(std::launch::async, [this, symbol, limit]() {
auto start = now_ns();
std::string url = service_url_ + "/trades/" + config_.exchange_id + "/" + symbol;
if (limit) url += "?limit=" + std::to_string(*limit);
auto response = cpr::Get(cpr::Url{url});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to get trades: " + response.text);
}
auto data = json::parse(response.text);
std::vector<Trade> trades;
for (const auto& t : data) {
Trade trade;
trade.trade_id = t.value("id", "");
trade.order_id = t.value("order", "");
trade.symbol = t.value("symbol", symbol);
trade.venue = name_;
trade.side = (t.value("side", "buy") == "buy") ? Side::Buy : Side::Sell;
trade.price = Decimal::from_double(t.value("price", 0.0));
trade.quantity = Decimal::from_double(t.value("amount", 0.0));
auto fee = t.value("fee", json{});
trade.fee.asset = fee.value("currency", "");
trade.fee.amount = Decimal::from_double(fee.value("cost", 0.0));
trade.timestamp = t.value("timestamp", int64_t(0));
trade.is_maker = (t.value("takerOrMaker", "taker") == "maker");
trades.push_back(std::move(trade));
}
return trades;
});
}
std::future<std::vector<Balance>> CcxtAdapter::get_balances() {
return std::async(std::launch::async, [this]() {
auto start = now_ns();
json body = {
{"exchange", config_.exchange_id},
{"apiKey", config_.api_key.value_or("")},
{"secret", config_.api_secret.value_or("")}
};
auto response = cpr::Post(
cpr::Url{service_url_ + "/balance"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{body.dump()});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to get balances: " + response.text);
}
auto data = json::parse(response.text);
std::vector<Balance> balances;
auto total = data.value("total", json{});
auto free = data.value("free", json{});
auto used = data.value("used", json{});
for (auto& [asset, amount] : total.items()) {
if (amount.is_null() || amount.get<double>() <= 0) continue;
Balance bal;
bal.asset = asset;
bal.venue = name_;
bal.free = Decimal::from_double(free.value(asset, 0.0));
bal.locked = Decimal::from_double(used.value(asset, 0.0));
balances.push_back(std::move(bal));
}
return balances;
});
}
std::future<Balance> CcxtAdapter::get_balance(const std::string& asset) {
return std::async(std::launch::async, [this, asset]() {
auto balances = get_balances().get();
for (const auto& b : balances) {
if (b.asset == asset) return b;
}
return Balance{asset, name_, Decimal::zero(), Decimal::zero()};
});
}
std::future<std::vector<Order>> CcxtAdapter::get_open_orders(
const std::optional<std::string>& symbol) {
return std::async(std::launch::async, [this, symbol]() {
auto start = now_ns();
json body = {
{"exchange", config_.exchange_id},
{"apiKey", config_.api_key.value_or("")},
{"secret", config_.api_secret.value_or("")}
};
if (symbol) body["symbol"] = *symbol;
auto response = cpr::Post(
cpr::Url{service_url_ + "/openOrders"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{body.dump()});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to get open orders: " + response.text);
}
auto data = json::parse(response.text);
std::vector<Order> orders;
for (const auto& o : data) {
orders.push_back(convert_order(o));
}
return orders;
});
}
Order CcxtAdapter::convert_order(const json& o) {
Order order;
order.order_id = o.value("id", "");
order.client_order_id = o.value("clientOrderId", "");
order.symbol = o.value("symbol", "");
order.venue = name_;
order.side = (o.value("side", "buy") == "buy") ? Side::Buy : Side::Sell;
std::string type_str = o.value("type", "limit");
if (type_str == "market") order.order_type = OrderType::Market;
else if (type_str == "limit") order.order_type = OrderType::Limit;
else if (type_str == "stop") order.order_type = OrderType::StopLoss;
else if (type_str == "stop_limit") order.order_type = OrderType::StopLossLimit;
std::string status_str = o.value("status", "open");
if (status_str == "open") order.status = OrderStatus::Open;
else if (status_str == "closed") order.status = OrderStatus::Filled;
else if (status_str == "canceled" || status_str == "cancelled")
order.status = OrderStatus::Cancelled;
else if (status_str == "expired") order.status = OrderStatus::Expired;
else if (status_str == "rejected") order.status = OrderStatus::Rejected;
order.quantity = Decimal::from_double(o.value("amount", 0.0));
order.filled_quantity = Decimal::from_double(o.value("filled", 0.0));
order.remaining_quantity = order.quantity - order.filled_quantity;
if (o.contains("price") && !o["price"].is_null())
order.price = Decimal::from_double(o["price"].get<double>());
if (o.contains("average") && !o["average"].is_null())
order.average_price = Decimal::from_double(o["average"].get<double>());
order.created_at = o.value("timestamp", int64_t(0));
order.updated_at = o.value("lastTradeTimestamp", order.created_at);
return order;
}
std::future<Order> CcxtAdapter::place_order(const OrderRequest& request) {
return std::async(std::launch::async, [this, request]() -> Order {
auto start = now_ns();
json body = {
{"exchange", config_.exchange_id},
{"apiKey", config_.api_key.value_or("")},
{"secret", config_.api_secret.value_or("")},
{"symbol", request.symbol},
{"side", request.side == Side::Buy ? "buy" : "sell"},
{"type", request.order_type == OrderType::Market ? "market" : "limit"},
{"amount", request.quantity.to_double()}
};
if (request.price) {
body["price"] = request.price->to_double();
}
if (!request.client_order_id.empty()) {
body["clientOrderId"] = request.client_order_id;
}
auto response = cpr::Post(
cpr::Url{service_url_ + "/order"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{body.dump()});
update_latency(start);
if (response.status_code != 200 && response.status_code != 201) {
throw AdapterError("Failed to place order: " + response.text);
}
auto data = json::parse(response.text);
return convert_order(data);
});
}
std::future<Order> CcxtAdapter::cancel_order(
const std::string& order_id, const std::string& symbol) {
return std::async(std::launch::async, [this, order_id, symbol]() -> Order {
auto start = now_ns();
json body = {
{"exchange", config_.exchange_id},
{"apiKey", config_.api_key.value_or("")},
{"secret", config_.api_secret.value_or("")},
{"orderId", order_id},
{"symbol", symbol}
};
auto response = cpr::Post(
cpr::Url{service_url_ + "/cancelOrder"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{body.dump()});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to cancel order: " + response.text);
}
auto data = json::parse(response.text);
return convert_order(data);
});
}
std::future<std::vector<Order>> CcxtAdapter::cancel_all_orders(
const std::optional<std::string>& symbol) {
return std::async(std::launch::async, [this, symbol]() {
// Get open orders then cancel each
auto open = get_open_orders(symbol).get();
std::vector<Order> cancelled;
for (const auto& order : open) {
try {
auto result = cancel_order(order.order_id, order.symbol).get();
cancelled.push_back(std::move(result));
} catch (...) {
// Continue with other orders
}
}
return cancelled;
});
}
} // namespace lx::trading
-401
View File
@@ -1,401 +0,0 @@
// LX Trading SDK - Unified Client Implementation
#include <lx/trading/client.hpp>
#include <lx/trading/adapters/native.hpp>
#include <lx/trading/adapters/ccxt.hpp>
#include <lx/trading/adapters/hummingbot.hpp>
#include <lx/trading/risk.hpp>
namespace lx::trading {
Client::Client(const Config& config)
: config_(config),
risk_manager_(std::make_unique<RiskManager>(config.risk)) {}
Client::~Client() = default;
std::future<void> Client::connect() {
return std::async(std::launch::async, [this]() {
// Connect native venues
for (const auto& [name, cfg] : config_.native) {
std::unique_ptr<VenueAdapter> adapter;
if (cfg.venue_type == "dex") {
adapter = std::make_unique<LxDexAdapter>(name, cfg);
} else {
adapter = std::make_unique<LxAmmAdapter>(name, cfg);
}
adapter->connect().get();
venues_[name] = std::move(adapter);
}
// Connect CCXT exchanges
for (const auto& [name, cfg] : config_.ccxt) {
auto adapter = std::make_unique<CcxtAdapter>(name, cfg);
adapter->connect().get();
venues_[name] = std::move(adapter);
}
// Connect Hummingbot gateways
for (const auto& [name, cfg] : config_.hummingbot) {
auto adapter = std::make_unique<HummingbotAdapter>(name, cfg);
adapter->connect().get();
venues_[name] = std::move(adapter);
}
// Set default venue
if (!config_.general.venue_priority.empty()) {
default_venue_ = config_.general.venue_priority[0];
} else if (!venues_.empty()) {
default_venue_ = venues_.begin()->first;
}
});
}
std::future<void> Client::disconnect() {
return std::async(std::launch::async, [this]() {
for (auto& [name, adapter] : venues_) {
adapter->disconnect().get();
}
venues_.clear();
default_venue_.reset();
});
}
VenueAdapter* Client::venue(std::string_view name) {
auto it = venues_.find(std::string(name));
return it != venues_.end() ? it->second.get() : nullptr;
}
const VenueAdapter* Client::venue(std::string_view name) const {
auto it = venues_.find(std::string(name));
return it != venues_.end() ? it->second.get() : nullptr;
}
std::vector<VenueInfo> Client::venues() const {
std::vector<VenueInfo> result;
for (const auto& [name, adapter] : venues_) {
result.push_back(adapter->info());
}
return result;
}
VenueAdapter* Client::get_venue(std::optional<std::string_view> name) {
std::string venue_name;
if (name) {
venue_name = std::string(*name);
} else if (default_venue_) {
venue_name = *default_venue_;
} else {
throw AdapterError("No venue specified and no default venue");
}
auto it = venues_.find(venue_name);
if (it == venues_.end()) {
throw AdapterError("Venue not found: " + venue_name);
}
return it->second.get();
}
// Market Data
std::future<std::unique_ptr<Orderbook>> Client::orderbook(
const std::string& symbol, std::optional<std::string_view> venue) {
return get_venue(venue)->get_orderbook(symbol);
}
std::future<AggregatedOrderbook> Client::aggregated_orderbook(const std::string& symbol) {
return std::async(std::launch::async, [this, symbol]() {
AggregatedOrderbook agg(symbol);
for (auto& [name, adapter] : venues_) {
if (adapter->capabilities().orderbook) {
try {
auto book = adapter->get_orderbook(symbol, 20).get();
agg.add_orderbook(*book);
} catch (...) {
// Skip venues that don't support this pair
}
}
}
return agg;
});
}
std::future<Ticker> Client::ticker(
const std::string& symbol, std::optional<std::string_view> venue) {
return get_venue(venue)->get_ticker(symbol);
}
std::future<std::vector<Ticker>> Client::tickers(const std::string& symbol) {
return std::async(std::launch::async, [this, symbol]() {
std::vector<Ticker> result;
for (auto& [name, adapter] : venues_) {
try {
auto t = adapter->get_ticker(symbol).get();
result.push_back(std::move(t));
} catch (...) {
// Skip
}
}
return result;
});
}
// Account
std::future<std::vector<AggregatedBalance>> Client::balances() {
return std::async(std::launch::async, [this]() {
std::unordered_map<std::string, std::vector<Balance>> by_asset;
for (auto& [name, adapter] : venues_) {
try {
for (auto& bal : adapter->get_balances().get()) {
by_asset[bal.asset].push_back(std::move(bal));
}
} catch (...) {
// Skip
}
}
std::vector<AggregatedBalance> result;
for (auto& [asset, bals] : by_asset) {
AggregatedBalance agg;
agg.asset = asset;
agg.by_venue = std::move(bals);
for (const auto& b : agg.by_venue) {
agg.total_free = agg.total_free + b.free;
agg.total_locked = agg.total_locked + b.locked;
}
result.push_back(std::move(agg));
}
return result;
});
}
std::future<Balance> Client::balance(
const std::string& asset, std::optional<std::string_view> venue) {
return get_venue(venue)->get_balance(asset);
}
// Orders
std::future<Order> Client::buy(
const std::string& symbol, Decimal quantity, std::optional<std::string_view> venue) {
OrderRequest req = OrderRequest::market(symbol, Side::Buy, quantity);
if (venue) req.venue = std::string(*venue);
return place_order(req);
}
std::future<Order> Client::sell(
const std::string& symbol, Decimal quantity, std::optional<std::string_view> venue) {
OrderRequest req = OrderRequest::market(symbol, Side::Sell, quantity);
if (venue) req.venue = std::string(*venue);
return place_order(req);
}
std::future<Order> Client::limit_buy(
const std::string& symbol, Decimal quantity, Decimal price,
std::optional<std::string_view> venue) {
OrderRequest req = OrderRequest::limit(symbol, Side::Buy, quantity, price);
if (venue) req.venue = std::string(*venue);
return place_order(req);
}
std::future<Order> Client::limit_sell(
const std::string& symbol, Decimal quantity, Decimal price,
std::optional<std::string_view> venue) {
OrderRequest req = OrderRequest::limit(symbol, Side::Sell, quantity, price);
if (venue) req.venue = std::string(*venue);
return place_order(req);
}
std::future<Order> Client::place_order(const OrderRequest& request) {
return std::async(std::launch::async, [this, request]() {
// Validate with risk manager
risk_manager_->validate_order(request);
// Route order
if (request.venue) {
auto adapter = get_venue(request.venue);
auto order = adapter->place_order(request).get();
risk_manager_->order_opened(request.symbol);
return order;
}
if (config_.general.smart_routing &&
request.order_type == OrderType::Market) {
return smart_route(request);
}
auto adapter = get_venue(std::nullopt);
auto order = adapter->place_order(request).get();
risk_manager_->order_opened(request.symbol);
return order;
});
}
Order Client::smart_route(const OrderRequest& request) {
auto agg_book = aggregated_orderbook(request.symbol).get();
std::optional<std::pair<std::string, Decimal>> best;
if (request.side == Side::Buy) {
best = agg_book.best_venue_buy(request.quantity);
} else {
best = agg_book.best_venue_sell(request.quantity);
}
if (best) {
OrderRequest routed = request;
routed.venue = best->first;
auto adapter = get_venue(routed.venue);
auto order = adapter->place_order(routed).get();
risk_manager_->order_opened(request.symbol);
return order;
}
// Fallback to default
auto adapter = get_venue(std::nullopt);
auto order = adapter->place_order(request).get();
risk_manager_->order_opened(request.symbol);
return order;
}
std::future<Order> Client::cancel_order(
const std::string& order_id, const std::string& symbol, const std::string& venue) {
return std::async(std::launch::async, [this, order_id, symbol, venue]() {
auto adapter = get_venue(venue);
auto order = adapter->cancel_order(order_id, symbol).get();
risk_manager_->order_closed(symbol);
return order;
});
}
std::future<std::vector<Order>> Client::cancel_all_orders(
std::optional<std::string_view> symbol, std::optional<std::string_view> venue) {
return std::async(std::launch::async, [this, symbol, venue]() {
std::optional<std::string> sym;
if (symbol) sym = std::string(*symbol);
if (venue) {
auto adapter = get_venue(venue);
auto orders = adapter->cancel_all_orders(sym).get();
for (const auto& o : orders) {
risk_manager_->order_closed(o.symbol);
}
return orders;
}
std::vector<Order> all_orders;
for (auto& [name, adapter] : venues_) {
try {
auto orders = adapter->cancel_all_orders(sym).get();
for (const auto& o : orders) {
risk_manager_->order_closed(o.symbol);
}
all_orders.insert(all_orders.end(), orders.begin(), orders.end());
} catch (...) {
// Continue
}
}
return all_orders;
});
}
std::future<std::vector<Order>> Client::open_orders(std::optional<std::string_view> symbol) {
return std::async(std::launch::async, [this, symbol]() {
std::optional<std::string> sym;
if (symbol) sym = std::string(*symbol);
std::vector<Order> all_orders;
for (auto& [name, adapter] : venues_) {
try {
auto orders = adapter->get_open_orders(sym).get();
all_orders.insert(all_orders.end(), orders.begin(), orders.end());
} catch (...) {
// Continue
}
}
return all_orders;
});
}
// AMM Operations
std::future<SwapQuote> Client::quote(
const std::string& base_token, const std::string& quote_token,
Decimal amount, bool is_buy, const std::string& venue) {
return get_venue(venue)->get_swap_quote(base_token, quote_token, amount, is_buy);
}
std::future<Trade> Client::swap(
const std::string& base_token, const std::string& quote_token,
Decimal amount, bool is_buy, double slippage, const std::string& venue) {
return get_venue(venue)->execute_swap(
base_token, quote_token, amount, is_buy, Decimal::from_double(slippage));
}
std::future<PoolInfo> Client::pool_info(
const std::string& base_token, const std::string& quote_token,
const std::string& venue) {
return get_venue(venue)->get_pool_info(base_token, quote_token);
}
std::future<LiquidityResult> Client::add_liquidity(
const std::string& base_token, const std::string& quote_token,
Decimal base_amount, Decimal quote_amount,
double slippage, const std::string& venue) {
return get_venue(venue)->add_liquidity(
base_token, quote_token, base_amount, quote_amount,
Decimal::from_double(slippage));
}
std::future<LiquidityResult> Client::remove_liquidity(
const std::string& pool_address, Decimal liquidity_amount,
double slippage, const std::string& venue) {
return get_venue(venue)->remove_liquidity(
pool_address, liquidity_amount, Decimal::from_double(slippage));
}
std::future<std::vector<LpPosition>> Client::lp_positions(const std::string& venue) {
return get_venue(venue)->get_lp_positions();
}
// Streaming
void Client::subscribe_ticker(
const std::string& symbol, VenueAdapter::TickerCallback callback,
std::optional<std::string_view> venue) {
get_venue(venue)->subscribe_ticker(symbol, std::move(callback));
}
void Client::subscribe_trades(
const std::string& symbol, VenueAdapter::TradeCallback callback,
std::optional<std::string_view> venue) {
get_venue(venue)->subscribe_trades(symbol, std::move(callback));
}
void Client::subscribe_orderbook(
const std::string& symbol, VenueAdapter::OrderbookCallback callback,
std::optional<std::string_view> venue) {
get_venue(venue)->subscribe_orderbook(symbol, std::move(callback));
}
void Client::subscribe_orders(VenueAdapter::OrderCallback callback) {
for (auto& [name, adapter] : venues_) {
adapter->subscribe_orders(callback);
}
}
void Client::unsubscribe_all() {
for (auto& [name, adapter] : venues_) {
adapter->unsubscribe_all();
}
}
} // namespace lx::trading
-135
View File
@@ -1,135 +0,0 @@
// LX Trading SDK - Configuration Implementation
#include <lx/trading/config.hpp>
#include <nlohmann/json.hpp>
#include <fstream>
#include <sstream>
namespace lx::trading {
// Simple TOML parser (handles basic cases)
namespace {
std::string trim(const std::string& s) {
size_t start = s.find_first_not_of(" \t\r\n");
if (start == std::string::npos) return "";
size_t end = s.find_last_not_of(" \t\r\n");
return s.substr(start, end - start + 1);
}
std::string unquote(const std::string& s) {
if (s.size() >= 2 && s[0] == '"' && s.back() == '"') {
return s.substr(1, s.size() - 2);
}
return s;
}
} // namespace
Config Config::from_file(std::string_view path) {
std::string path_str{path};
std::ifstream file{path_str};
if (!file.is_open()) {
throw std::runtime_error("Cannot open config file: " + path_str);
}
std::stringstream buffer;
buffer << file.rdbuf();
return from_toml(buffer.str());
}
Config Config::from_toml(std::string_view content) {
Config config;
std::string current_section;
std::string current_subsection;
std::string content_str{content};
std::istringstream stream{content_str};
std::string line;
while (std::getline(stream, line)) {
line = trim(line);
// Skip empty lines and comments
if (line.empty() || line[0] == '#') continue;
// Section header
if (line[0] == '[') {
size_t end = line.find(']');
if (end != std::string::npos) {
std::string section = line.substr(1, end - 1);
// Check for subsection [section.name]
auto dot = section.find('.');
if (dot != std::string::npos) {
current_section = section.substr(0, dot);
current_subsection = section.substr(dot + 1);
} else {
current_section = section;
current_subsection.clear();
}
}
continue;
}
// Key-value pair
auto eq = line.find('=');
if (eq != std::string::npos) {
std::string key = trim(line.substr(0, eq));
std::string value = trim(line.substr(eq + 1));
value = unquote(value);
// Parse based on section
if (current_section == "general") {
if (key == "log_level") config.general.log_level = value;
else if (key == "timeout_ms") config.general.timeout_ms = std::stoi(value);
else if (key == "smart_routing") config.general.smart_routing = (value == "true");
else if (key == "min_improvement_bps") config.general.min_improvement_bps = std::stoi(value);
}
else if (current_section == "risk") {
if (key == "enabled") config.risk.enabled = (value == "true");
else if (key == "max_position_size") config.risk.max_position_size = Decimal::from_string(value);
else if (key == "max_order_size") config.risk.max_order_size = Decimal::from_string(value);
else if (key == "max_daily_loss") config.risk.max_daily_loss = Decimal::from_string(value);
else if (key == "max_open_orders") config.risk.max_open_orders = std::stoi(value);
else if (key == "kill_switch_enabled") config.risk.kill_switch_enabled = (value == "true");
}
else if (current_section == "native" && !current_subsection.empty()) {
auto& native_cfg = config.native[current_subsection];
if (key == "venue_type") native_cfg.venue_type = value;
else if (key == "api_url") native_cfg.api_url = value;
else if (key == "ws_url") native_cfg.ws_url = value;
else if (key == "api_key") native_cfg.api_key = value;
else if (key == "api_secret") native_cfg.api_secret = value;
else if (key == "wallet_address") native_cfg.wallet_address = value;
else if (key == "private_key") native_cfg.private_key = value;
else if (key == "network") native_cfg.network = value;
else if (key == "chain_id") native_cfg.chain_id = std::stoi(value);
else if (key == "streaming") native_cfg.streaming = (value == "true");
}
else if (current_section == "ccxt" && !current_subsection.empty()) {
auto& ccxt_cfg = config.ccxt[current_subsection];
if (key == "exchange_id") ccxt_cfg.exchange_id = value;
else if (key == "api_key") ccxt_cfg.api_key = value;
else if (key == "api_secret") ccxt_cfg.api_secret = value;
else if (key == "password") ccxt_cfg.password = value;
else if (key == "sandbox") ccxt_cfg.sandbox = (value == "true");
else if (key == "rate_limit") ccxt_cfg.rate_limit = (value == "true");
}
else if (current_section == "hummingbot" && !current_subsection.empty()) {
auto& hb_cfg = config.hummingbot[current_subsection];
if (key == "host") hb_cfg.host = value;
else if (key == "port") hb_cfg.port = std::stoi(value);
else if (key == "https") hb_cfg.https = (value == "true");
else if (key == "connector") hb_cfg.connector = value;
else if (key == "chain") hb_cfg.chain = value;
else if (key == "network") hb_cfg.network = value;
else if (key == "wallet_address") hb_cfg.wallet_address = value;
}
}
}
return config;
}
} // namespace lx::trading
-469
View File
@@ -1,469 +0,0 @@
// LX Trading SDK - Execution Algorithms Implementation
#include <lx/trading/execution.hpp>
#include <lx/trading/client.hpp>
#include <thread>
namespace lx::trading {
// =============================================================================
// TWAP Executor
// =============================================================================
TwapExecutor::TwapExecutor(
Client& client,
std::string symbol,
Side side,
Decimal total_quantity,
std::chrono::seconds duration,
int num_slices)
: client_(client),
symbol_(std::move(symbol)),
side_(side),
total_quantity_(total_quantity),
duration_(duration),
num_slices_(num_slices) {}
std::future<ExecutionResult> TwapExecutor::execute() {
return std::async(std::launch::async, [this]() {
ExecutionResult result;
result.start_time = now_ms();
result.total_quantity = total_quantity_;
result.completed = false;
Decimal slice_qty = total_quantity_ / Decimal::from_double(num_slices_);
auto interval = std::chrono::milliseconds(
duration_.count() * 1000 / num_slices_);
Decimal total_value;
for (int i = 0; i < num_slices_; ++i) {
if (cancelled_.load(std::memory_order_acquire)) {
result.error = "Cancelled";
break;
}
Decimal remaining = total_quantity_ - (slice_qty * Decimal::from_double(i));
Decimal qty = (remaining < slice_qty) ? remaining : slice_qty;
if (qty <= Decimal::zero()) break;
try {
Order order;
if (side_ == Side::Buy) {
order = client_.buy(symbol_, qty).get();
} else {
order = client_.sell(symbol_, qty).get();
}
result.orders.push_back(order);
result.total_filled = result.total_filled + order.filled_quantity;
if (order.average_price) {
total_value = total_value +
(order.filled_quantity * *order.average_price);
}
if (callback_) {
callback_(order, remaining - qty);
}
} catch (const std::exception& e) {
result.error = e.what();
break;
}
if (i < num_slices_ - 1) {
std::this_thread::sleep_for(interval);
}
}
result.end_time = now_ms();
result.completed = result.error.empty();
if (result.total_filled.is_positive()) {
result.average_price = total_value / result.total_filled;
}
return result;
});
}
void TwapExecutor::cancel() {
cancelled_.store(true, std::memory_order_release);
}
// =============================================================================
// VWAP Executor
// =============================================================================
VwapExecutor::VwapExecutor(
Client& client,
std::string symbol,
Side side,
Decimal total_quantity,
Decimal participation_rate,
std::chrono::seconds max_duration)
: client_(client),
symbol_(std::move(symbol)),
side_(side),
total_quantity_(total_quantity),
participation_rate_(participation_rate),
max_duration_(max_duration) {}
std::future<ExecutionResult> VwapExecutor::execute() {
return std::async(std::launch::async, [this]() {
ExecutionResult result;
result.start_time = now_ms();
result.total_quantity = total_quantity_;
result.completed = false;
Decimal remaining = total_quantity_;
const int check_interval_ms = 5000;
int elapsed_ms = 0;
Decimal total_value;
while (remaining > Decimal::zero() &&
elapsed_ms < max_duration_.count() * 1000) {
if (cancelled_.load(std::memory_order_acquire)) {
result.error = "Cancelled";
break;
}
try {
auto ticker = client_.ticker(symbol_).get();
Decimal volume = ticker.volume_24h.value_or(Decimal::from_double(1000));
// Calculate slice based on participation
Decimal hourly_volume = volume / Decimal::from_double(24);
Decimal slice_volume = hourly_volume * participation_rate_ /
Decimal::from_double(3600000.0 / check_interval_ms);
Decimal qty = (remaining < slice_volume) ? remaining : slice_volume;
if (qty > Decimal::zero()) {
Order order;
if (side_ == Side::Buy) {
order = client_.buy(symbol_, qty).get();
} else {
order = client_.sell(symbol_, qty).get();
}
result.orders.push_back(order);
result.total_filled = result.total_filled + order.filled_quantity;
remaining = remaining - order.filled_quantity;
if (order.average_price) {
total_value = total_value +
(order.filled_quantity * *order.average_price);
}
if (callback_) {
callback_(order, remaining);
}
}
} catch (const std::exception& e) {
// Log but continue
}
std::this_thread::sleep_for(std::chrono::milliseconds(check_interval_ms));
elapsed_ms += check_interval_ms;
}
result.end_time = now_ms();
result.completed = result.error.empty() && remaining <= Decimal::zero();
if (result.total_filled.is_positive()) {
result.average_price = total_value / result.total_filled;
}
return result;
});
}
void VwapExecutor::cancel() {
cancelled_.store(true, std::memory_order_release);
}
// =============================================================================
// Iceberg Executor
// =============================================================================
IcebergExecutor::IcebergExecutor(
Client& client,
std::string symbol,
Side side,
Decimal total_quantity,
Decimal visible_quantity,
Decimal price,
std::optional<std::string> venue)
: client_(client),
symbol_(std::move(symbol)),
side_(side),
total_quantity_(total_quantity),
visible_quantity_(visible_quantity),
price_(price),
venue_(std::move(venue)) {}
std::future<ExecutionResult> IcebergExecutor::execute() {
return std::async(std::launch::async, [this]() {
ExecutionResult result;
result.start_time = now_ms();
result.total_quantity = total_quantity_;
result.completed = false;
Decimal remaining = total_quantity_;
Decimal total_value;
while (remaining > Decimal::zero()) {
if (cancelled_.load(std::memory_order_acquire)) {
result.error = "Cancelled";
break;
}
Decimal qty = (remaining < visible_quantity_) ? remaining : visible_quantity_;
try {
Order order;
if (side_ == Side::Buy) {
order = client_.limit_buy(
symbol_, qty, price_,
venue_ ? std::optional<std::string_view>(*venue_)
: std::nullopt).get();
} else {
order = client_.limit_sell(
symbol_, qty, price_,
venue_ ? std::optional<std::string_view>(*venue_)
: std::nullopt).get();
}
// Wait for fill (simplified - in production would poll/stream)
std::this_thread::sleep_for(std::chrono::milliseconds(500));
result.orders.push_back(order);
result.total_filled = result.total_filled + order.filled_quantity;
remaining = remaining - order.filled_quantity;
if (order.average_price) {
total_value = total_value +
(order.filled_quantity * *order.average_price);
}
if (callback_) {
callback_(order, remaining);
}
} catch (const std::exception& e) {
result.error = e.what();
break;
}
}
result.end_time = now_ms();
result.completed = result.error.empty() && remaining <= Decimal::zero();
if (result.total_filled.is_positive()) {
result.average_price = total_value / result.total_filled;
}
return result;
});
}
void IcebergExecutor::cancel() {
cancelled_.store(true, std::memory_order_release);
}
// =============================================================================
// Sniper Executor
// =============================================================================
SniperExecutor::SniperExecutor(
Client& client,
std::string symbol,
Side side,
Decimal quantity,
Decimal target_price,
std::chrono::seconds timeout)
: client_(client),
symbol_(std::move(symbol)),
side_(side),
quantity_(quantity),
target_price_(target_price),
timeout_(timeout) {}
std::future<ExecutionResult> SniperExecutor::execute() {
return std::async(std::launch::async, [this]() {
ExecutionResult result;
result.start_time = now_ms();
result.total_quantity = quantity_;
result.completed = false;
const int check_interval_ms = 100;
int elapsed_ms = 0;
while (elapsed_ms < timeout_.count() * 1000) {
if (cancelled_.load(std::memory_order_acquire)) {
result.error = "Cancelled";
break;
}
try {
auto ticker = client_.ticker(symbol_).get();
bool should_execute = false;
if (side_ == Side::Buy) {
if (ticker.ask && *ticker.ask <= target_price_) {
should_execute = true;
}
} else {
if (ticker.bid && *ticker.bid >= target_price_) {
should_execute = true;
}
}
if (should_execute) {
Order order;
if (side_ == Side::Buy) {
order = client_.buy(symbol_, quantity_).get();
} else {
order = client_.sell(symbol_, quantity_).get();
}
result.orders.push_back(order);
result.total_filled = order.filled_quantity;
result.average_price = order.average_price;
result.completed = true;
result.end_time = now_ms();
if (callback_) {
callback_(order, Decimal::zero());
}
return result;
}
} catch (const std::exception&) {
// Continue waiting
}
std::this_thread::sleep_for(std::chrono::milliseconds(check_interval_ms));
elapsed_ms += check_interval_ms;
}
result.end_time = now_ms();
if (result.error.empty() && !result.completed) {
result.error = "Timeout";
}
return result;
});
}
void SniperExecutor::cancel() {
cancelled_.store(true, std::memory_order_release);
}
// =============================================================================
// POV Executor
// =============================================================================
PovExecutor::PovExecutor(
Client& client,
std::string symbol,
Side side,
Decimal total_quantity,
Decimal target_participation,
std::chrono::seconds max_duration,
std::optional<Decimal> price_limit)
: client_(client),
symbol_(std::move(symbol)),
side_(side),
total_quantity_(total_quantity),
target_participation_(target_participation),
max_duration_(max_duration),
price_limit_(price_limit) {}
std::future<ExecutionResult> PovExecutor::execute() {
return std::async(std::launch::async, [this]() {
ExecutionResult result;
result.start_time = now_ms();
result.total_quantity = total_quantity_;
result.completed = false;
// Similar to VWAP but with stricter participation targeting
Decimal remaining = total_quantity_;
const int check_interval_ms = 5000;
int elapsed_ms = 0;
Decimal total_value;
while (remaining > Decimal::zero() &&
elapsed_ms < max_duration_.count() * 1000) {
if (cancelled_.load(std::memory_order_acquire)) {
result.error = "Cancelled";
break;
}
try {
auto ticker = client_.ticker(symbol_).get();
// Check price limit
if (price_limit_) {
if (side_ == Side::Buy && ticker.ask && *ticker.ask > *price_limit_) {
std::this_thread::sleep_for(std::chrono::milliseconds(check_interval_ms));
elapsed_ms += check_interval_ms;
continue;
}
if (side_ == Side::Sell && ticker.bid && *ticker.bid < *price_limit_) {
std::this_thread::sleep_for(std::chrono::milliseconds(check_interval_ms));
elapsed_ms += check_interval_ms;
continue;
}
}
Decimal volume = ticker.volume_24h.value_or(Decimal::from_double(1000));
Decimal interval_volume = volume / Decimal::from_double(24 * 3600000.0 / check_interval_ms);
Decimal target_qty = interval_volume * target_participation_;
Decimal qty = (remaining < target_qty) ? remaining : target_qty;
if (qty > Decimal::zero()) {
Order order;
if (side_ == Side::Buy) {
order = client_.buy(symbol_, qty).get();
} else {
order = client_.sell(symbol_, qty).get();
}
result.orders.push_back(order);
result.total_filled = result.total_filled + order.filled_quantity;
remaining = remaining - order.filled_quantity;
if (order.average_price) {
total_value = total_value +
(order.filled_quantity * *order.average_price);
}
if (callback_) {
callback_(order, remaining);
}
}
} catch (const std::exception&) {
// Continue
}
std::this_thread::sleep_for(std::chrono::milliseconds(check_interval_ms));
elapsed_ms += check_interval_ms;
}
result.end_time = now_ms();
result.completed = result.error.empty() && remaining <= Decimal::zero();
if (result.total_filled.is_positive()) {
result.average_price = total_value / result.total_filled;
}
return result;
});
}
void PovExecutor::cancel() {
cancelled_.store(true, std::memory_order_release);
}
} // namespace lx::trading
@@ -1,504 +0,0 @@
// LX Trading SDK - Hummingbot Adapter Implementation
#include <lx/trading/adapters/hummingbot.hpp>
#include <lx/trading/orderbook.hpp>
#include <cpr/cpr.h>
#include <nlohmann/json.hpp>
namespace lx::trading {
using json = nlohmann::json;
HummingbotAdapter::HummingbotAdapter(std::string_view name, const HummingbotConfig& config)
: name_(name), config_(config), capabilities_(VenueCapabilities::amm()) {}
HummingbotAdapter::~HummingbotAdapter() = default;
void HummingbotAdapter::update_latency(int64_t start_ns) {
int64_t elapsed = now_ns() - start_ns;
latency_.store(static_cast<int>(elapsed / 1000000), std::memory_order_release);
}
json HummingbotAdapter::build_request_body() {
json body = {
{"chain", config_.chain},
{"network", config_.network},
{"connector", config_.connector}
};
if (config_.wallet_address) {
body["address"] = *config_.wallet_address;
}
return body;
}
std::future<void> HummingbotAdapter::connect() {
return std::async(std::launch::async, [this]() {
auto start = now_ns();
auto response = cpr::Get(cpr::Url{config_.base_url()});
if (response.status_code != 200) {
throw AdapterError("Gateway not ready: " + response.text);
}
auto data = json::parse(response.text);
if (data.value("status", "") != "ok") {
throw AdapterError("Gateway not ready");
}
update_latency(start);
connected_.store(true, std::memory_order_release);
});
}
std::future<void> HummingbotAdapter::disconnect() {
return std::async(std::launch::async, [this]() {
connected_.store(false, std::memory_order_release);
});
}
std::future<std::vector<MarketInfo>> HummingbotAdapter::get_markets() {
return std::async(std::launch::async, [this]() {
auto start = now_ns();
auto response = cpr::Post(
cpr::Url{config_.base_url() + "/amm/tokens"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{build_request_body().dump()});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to get markets: " + response.text);
}
auto data = json::parse(response.text);
std::vector<MarketInfo> markets;
auto tokens = data.value("tokens", json::array());
for (size_t i = 0; i < tokens.size(); ++i) {
for (size_t j = i + 1; j < tokens.size(); ++j) {
std::string sym1 = tokens[i].value("symbol", "");
std::string sym2 = tokens[j].value("symbol", "");
if (sym1.empty() || sym2.empty()) continue;
MarketInfo info;
info.symbol = sym1 + "-" + sym2;
info.base = sym1;
info.quote = sym2;
info.price_precision = 8;
info.quantity_precision = 8;
info.tick_size = Decimal::from_double(0.00000001);
info.lot_size = Decimal::from_double(0.00000001);
markets.push_back(std::move(info));
}
}
return markets;
});
}
std::future<Ticker> HummingbotAdapter::get_ticker(const std::string& symbol) {
return std::async(std::launch::async, [this, symbol]() {
auto pair = TradingPair::from_symbol(symbol);
if (!pair) throw AdapterError("Invalid symbol: " + symbol);
std::string base(pair->base.data());
std::string quote(pair->quote.data());
auto start = now_ns();
json body = build_request_body();
body["base"] = base;
body["quote"] = quote;
body["amount"] = "1";
body["side"] = "BUY";
auto response = cpr::Post(
cpr::Url{config_.base_url() + "/amm/price"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{body.dump()});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to get ticker: " + response.text);
}
auto data = json::parse(response.text);
Ticker ticker;
ticker.symbol = symbol;
ticker.venue = name_;
if (data.contains("price") && !data["price"].is_null()) {
auto price = Decimal::from_string(data["price"].get<std::string>());
ticker.bid = price;
ticker.ask = price;
ticker.last = price;
}
ticker.timestamp = now_ms();
return ticker;
});
}
std::future<std::unique_ptr<Orderbook>> HummingbotAdapter::get_orderbook(
const std::string& symbol, std::optional<int> depth) {
(void)symbol; (void)depth;
return std::async(std::launch::deferred, []() -> std::unique_ptr<Orderbook> {
throw AdapterError("Gateway AMM does not have orderbook");
});
}
std::future<std::vector<Trade>> HummingbotAdapter::get_trades(
const std::string& symbol, std::optional<int> limit) {
(void)symbol; (void)limit;
return std::async(std::launch::deferred, []() {
return std::vector<Trade>{}; // Gateway doesn't provide trade history
});
}
std::future<std::vector<Balance>> HummingbotAdapter::get_balances() {
return std::async(std::launch::async, [this]() {
auto start = now_ns();
auto response = cpr::Post(
cpr::Url{config_.base_url() + "/chain/balances"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{build_request_body().dump()});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to get balances: " + response.text);
}
auto data = json::parse(response.text);
std::vector<Balance> balances;
auto bals = data.value("balances", json::object());
for (auto& [asset, amount] : bals.items()) {
Balance bal;
bal.asset = asset;
bal.venue = name_;
bal.free = Decimal::from_string(amount.get<std::string>());
bal.locked = Decimal::zero();
balances.push_back(std::move(bal));
}
return balances;
});
}
std::future<Balance> HummingbotAdapter::get_balance(const std::string& asset) {
return std::async(std::launch::async, [this, asset]() {
auto balances = get_balances().get();
for (const auto& b : balances) {
if (b.asset == asset) return b;
}
return Balance{asset, name_, Decimal::zero(), Decimal::zero()};
});
}
std::future<std::vector<Order>> HummingbotAdapter::get_open_orders(
const std::optional<std::string>& symbol) {
(void)symbol;
return std::async(std::launch::deferred, []() {
return std::vector<Order>{}; // AMM doesn't have orders
});
}
std::future<Order> HummingbotAdapter::place_order(const OrderRequest& request) {
return std::async(std::launch::async, [this, request]() {
auto pair = TradingPair::from_symbol(request.symbol);
if (!pair) throw AdapterError("Invalid symbol: " + request.symbol);
std::string base(pair->base.data());
std::string quote(pair->quote.data());
auto trade = execute_swap(
base, quote, request.quantity,
request.side == Side::Buy,
Decimal::from_double(0.01)).get();
Order order;
order.order_id = trade.trade_id;
order.client_order_id = request.client_order_id;
order.symbol = request.symbol;
order.venue = name_;
order.side = request.side;
order.order_type = OrderType::Market;
order.status = OrderStatus::Filled;
order.quantity = request.quantity;
order.filled_quantity = trade.quantity;
order.remaining_quantity = Decimal::zero();
order.price = trade.price;
order.average_price = trade.price;
order.created_at = trade.timestamp;
order.updated_at = trade.timestamp;
order.fees.push_back(trade.fee);
return order;
});
}
std::future<Order> HummingbotAdapter::cancel_order(
const std::string& order_id, const std::string& symbol) {
(void)order_id; (void)symbol;
return std::async(std::launch::deferred, []() -> Order {
throw AdapterError("Gateway AMM swaps cannot be cancelled");
});
}
std::future<std::vector<Order>> HummingbotAdapter::cancel_all_orders(
const std::optional<std::string>& symbol) {
(void)symbol;
return std::async(std::launch::deferred, []() {
return std::vector<Order>{};
});
}
std::future<SwapQuote> HummingbotAdapter::get_swap_quote(
const std::string& base_token,
const std::string& quote_token,
Decimal amount,
bool is_buy) {
return std::async(std::launch::async,
[this, base_token, quote_token, amount, is_buy]() {
auto start = now_ns();
json body = build_request_body();
body["base"] = base_token;
body["quote"] = quote_token;
body["amount"] = amount.to_string();
body["side"] = is_buy ? "BUY" : "SELL";
auto response = cpr::Post(
cpr::Url{config_.base_url() + "/amm/price"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{body.dump()});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to get quote: " + response.text);
}
auto data = json::parse(response.text);
SwapQuote quote;
quote.base_token = base_token;
quote.quote_token = quote_token;
quote.input_amount = amount;
quote.output_amount = Decimal::from_string(data.value("expectedAmount", "0"));
quote.price = Decimal::from_string(data.value("price", "0"));
quote.price_impact = Decimal::zero();
quote.fee = Decimal::zero();
quote.expires_at = now_ms() + 60000;
return quote;
});
}
std::future<Trade> HummingbotAdapter::execute_swap(
const std::string& base_token,
const std::string& quote_token,
Decimal amount,
bool is_buy,
Decimal slippage) {
return std::async(std::launch::async,
[this, base_token, quote_token, amount, is_buy, slippage]() {
auto start = now_ns();
json body = build_request_body();
body["base"] = base_token;
body["quote"] = quote_token;
body["amount"] = amount.to_string();
body["side"] = is_buy ? "BUY" : "SELL";
body["limitPrice"] = "";
body["allowedSlippage"] = slippage.to_string() + "/100";
auto response = cpr::Post(
cpr::Url{config_.base_url() + "/amm/trade"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{body.dump()});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to execute swap: " + response.text);
}
auto data = json::parse(response.text);
Trade trade;
trade.trade_id = data.value("txHash", "");
trade.order_id = trade.trade_id;
trade.symbol = base_token + "-" + quote_token;
trade.venue = name_;
trade.side = is_buy ? Side::Buy : Side::Sell;
trade.price = Decimal::from_string(data.value("price", "0"));
trade.quantity = amount;
trade.fee.asset = "GAS";
trade.fee.amount = Decimal::from_string(data.value("gasPrice", "0"));
trade.timestamp = now_ms();
trade.is_maker = false;
return trade;
});
}
std::future<PoolInfo> HummingbotAdapter::get_pool_info(
const std::string& base_token,
const std::string& quote_token) {
return std::async(std::launch::async, [this, base_token, quote_token]() {
auto start = now_ns();
json body = build_request_body();
body["token0"] = base_token;
body["token1"] = quote_token;
auto response = cpr::Post(
cpr::Url{config_.base_url() + "/amm/poolPrice"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{body.dump()});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to get pool info: " + response.text);
}
auto data = json::parse(response.text);
PoolInfo info;
info.address = data.value("token0Address", "");
info.base_token = base_token;
info.quote_token = quote_token;
info.base_reserve = Decimal::from_string(data.value("token0Balance", "0"));
info.quote_reserve = Decimal::from_string(data.value("token1Balance", "0"));
info.total_liquidity = Decimal::zero();
info.fee_rate = Decimal::from_double(0.003);
return info;
});
}
std::future<LiquidityResult> HummingbotAdapter::add_liquidity(
const std::string& base_token,
const std::string& quote_token,
Decimal base_amount,
Decimal quote_amount,
Decimal slippage) {
return std::async(std::launch::async,
[this, base_token, quote_token, base_amount, quote_amount, slippage]() {
auto start = now_ns();
json body = build_request_body();
body["token0"] = base_token;
body["token1"] = quote_token;
body["amount0"] = base_amount.to_string();
body["amount1"] = quote_amount.to_string();
body["allowedSlippage"] = slippage.to_string() + "/100";
auto response = cpr::Post(
cpr::Url{config_.base_url() + "/amm/liquidity/add"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{body.dump()});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to add liquidity: " + response.text);
}
auto data = json::parse(response.text);
LiquidityResult result;
result.tx_hash = data.value("txHash", "");
result.pool_address = data.value("poolAddress", "");
result.base_amount = base_amount;
result.quote_amount = quote_amount;
result.lp_tokens = Decimal::zero();
result.share_percent = Decimal::zero();
return result;
});
}
std::future<LiquidityResult> HummingbotAdapter::remove_liquidity(
const std::string& pool_address,
Decimal liquidity_amount,
Decimal slippage) {
return std::async(std::launch::async,
[this, pool_address, liquidity_amount, slippage]() {
auto start = now_ns();
json body = build_request_body();
body["tokenId"] = pool_address;
body["decreasePercent"] = "100";
body["allowedSlippage"] = slippage.to_string() + "/100";
auto response = cpr::Post(
cpr::Url{config_.base_url() + "/amm/liquidity/remove"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{body.dump()});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to remove liquidity: " + response.text);
}
auto data = json::parse(response.text);
LiquidityResult result;
result.tx_hash = data.value("txHash", "");
result.pool_address = pool_address;
result.base_amount = Decimal::zero();
result.quote_amount = Decimal::zero();
result.lp_tokens = liquidity_amount;
result.share_percent = Decimal::zero();
return result;
});
}
std::future<std::vector<LpPosition>> HummingbotAdapter::get_lp_positions() {
return std::async(std::launch::async, [this]() {
auto start = now_ns();
auto response = cpr::Post(
cpr::Url{config_.base_url() + "/amm/position"},
cpr::Header{{"Content-Type", "application/json"}},
cpr::Body{build_request_body().dump()});
update_latency(start);
if (response.status_code != 200) {
throw AdapterError("Failed to get LP positions: " + response.text);
}
auto data = json::parse(response.text);
std::vector<LpPosition> positions;
if (data.is_array()) {
for (const auto& p : data) {
LpPosition pos;
pos.pool_address = p.value("tokenId", "");
pos.base_token = p.value("token0", "");
pos.quote_token = p.value("token1", "");
pos.lp_tokens = Decimal::zero();
pos.base_amount = Decimal::from_string(p.value("amount0", "0"));
pos.quote_amount = Decimal::from_string(p.value("amount1", "0"));
pos.share_percent = Decimal::zero();
if (p.contains("unclaimedToken0") && !p["unclaimedToken0"].is_null()) {
pos.unrealized_pnl = Decimal::from_string(
p["unclaimedToken0"].get<std::string>());
}
positions.push_back(std::move(pos));
}
}
return positions;
});
}
} // namespace lx::trading
-546
View File
@@ -1,546 +0,0 @@
// LX Trading SDK - Financial Mathematics Implementation
// SIMD-optimized where beneficial
#include <lx/trading/math.hpp>
#include <algorithm>
#include <numeric>
namespace lx::trading::math {
// =============================================================================
// Black-Scholes Implementation
// =============================================================================
double black_scholes(
double S, double K, double T, double r, double sigma, bool is_call) noexcept {
if (T <= 0) {
return is_call ? std::max(S - K, 0.0) : std::max(K - S, 0.0);
}
double sqrt_T = std::sqrt(T);
double d1 = (std::log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * sqrt_T);
double d2 = d1 - sigma * sqrt_T;
if (is_call) {
return S * norm_cdf(d1) - K * std::exp(-r * T) * norm_cdf(d2);
}
return K * std::exp(-r * T) * norm_cdf(-d2) - S * norm_cdf(-d1);
}
double implied_volatility(
double price, double S, double K, double T, double r,
bool is_call, double tol, int max_iter) noexcept {
// Newton-Raphson
double sigma = 0.2;
for (int i = 0; i < max_iter; ++i) {
double bs_price = black_scholes(S, K, T, r, sigma, is_call);
// Vega
double sqrt_T = std::sqrt(T);
double d1 = (std::log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * sqrt_T);
double vega = S * norm_pdf(d1) * sqrt_T;
if (std::abs(vega) < 1e-10) break;
double diff = bs_price - price;
if (std::abs(diff) < tol) return sigma;
sigma -= diff / vega;
sigma = std::clamp(sigma, 0.001, 5.0);
}
return sigma;
}
Greeks greeks(
double S, double K, double T, double r, double sigma, bool is_call) noexcept {
Greeks g{0, 0, 0, 0, 0};
if (T <= 0) return g;
double sqrt_T = std::sqrt(T);
double d1 = (std::log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * sqrt_T);
double d2 = d1 - sigma * sqrt_T;
double pdf_d1 = norm_pdf(d1);
double cdf_d1 = norm_cdf(d1);
double cdf_d2 = norm_cdf(d2);
double cdf_neg_d1 = norm_cdf(-d1);
double cdf_neg_d2 = norm_cdf(-d2);
double exp_neg_rT = std::exp(-r * T);
if (is_call) {
g.delta = cdf_d1;
g.theta = (-S * pdf_d1 * sigma / (2 * sqrt_T) -
r * K * exp_neg_rT * cdf_d2);
g.rho = K * T * exp_neg_rT * cdf_d2;
} else {
g.delta = cdf_d1 - 1;
g.theta = (-S * pdf_d1 * sigma / (2 * sqrt_T) +
r * K * exp_neg_rT * cdf_neg_d2);
g.rho = -K * T * exp_neg_rT * cdf_neg_d2;
}
g.gamma = pdf_d1 / (S * sigma * sqrt_T);
g.vega = S * pdf_d1 * sqrt_T / 100; // Per 1% vol change
g.theta /= 365; // Daily theta
return g;
}
// =============================================================================
// AMM Pricing
// =============================================================================
std::pair<double, double> constant_product_price(
double reserve_x, double reserve_y,
double amount_in, double fee_rate, bool is_x_to_y) noexcept {
double amount_in_with_fee = amount_in * (1.0 - fee_rate);
double amount_out;
if (is_x_to_y) {
amount_out = (reserve_y * amount_in_with_fee) /
(reserve_x + amount_in_with_fee);
} else {
amount_out = (reserve_x * amount_in_with_fee) /
(reserve_y + amount_in_with_fee);
}
double effective_price = (amount_in > 0) ? amount_out / amount_in : 0;
return {amount_out, effective_price};
}
std::tuple<double, double, double> concentrated_liquidity_price(
double liquidity, double sqrt_price_current,
double sqrt_price_lower, double sqrt_price_upper,
double amount_in, double fee_rate, bool is_token0_in) noexcept {
double amount_in_with_fee = amount_in * (1.0 - fee_rate);
double new_sqrt_p;
double amount_out;
if (is_token0_in) {
// Swapping X for Y (price goes up)
double delta_inv_sqrt_p = amount_in_with_fee / liquidity;
double new_inv_sqrt_p = 1.0 / sqrt_price_current - delta_inv_sqrt_p;
if (new_inv_sqrt_p <= 0) {
new_sqrt_p = sqrt_price_upper;
} else {
new_sqrt_p = 1.0 / new_inv_sqrt_p;
}
new_sqrt_p = std::min(new_sqrt_p, sqrt_price_upper);
amount_out = liquidity * (new_sqrt_p - sqrt_price_current);
} else {
// Swapping Y for X (price goes down)
double delta_sqrt_p = amount_in_with_fee / liquidity;
new_sqrt_p = sqrt_price_current - delta_sqrt_p;
new_sqrt_p = std::max(new_sqrt_p, sqrt_price_lower);
amount_out = liquidity * (1.0 / new_sqrt_p - 1.0 / sqrt_price_current);
}
double old_price = sqrt_price_current * sqrt_price_current;
double new_price = new_sqrt_p * new_sqrt_p;
double price_impact = std::abs(new_price - old_price) / old_price;
return {std::max(amount_out, 0.0), new_sqrt_p, price_impact};
}
double calculate_liquidity(
double amount_x, double amount_y,
double sqrt_price_current,
double sqrt_price_lower, double sqrt_price_upper) noexcept {
if (sqrt_price_current <= sqrt_price_lower) {
// Only token X
return amount_x * sqrt_price_lower * sqrt_price_upper /
(sqrt_price_upper - sqrt_price_lower);
} else if (sqrt_price_current >= sqrt_price_upper) {
// Only token Y
return amount_y / (sqrt_price_upper - sqrt_price_lower);
} else {
// Both tokens
double l_x = amount_x * sqrt_price_current * sqrt_price_upper /
(sqrt_price_upper - sqrt_price_current);
double l_y = amount_y / (sqrt_price_current - sqrt_price_lower);
return std::min(l_x, l_y);
}
}
// =============================================================================
// Risk Metrics
// =============================================================================
double volatility(
const std::vector<double>& returns, bool annualize, int periods_per_year) noexcept {
if (returns.size() < 2) return 0.0;
double mean = std::accumulate(returns.begin(), returns.end(), 0.0) / returns.size();
double variance = 0.0;
for (double r : returns) {
double diff = r - mean;
variance += diff * diff;
}
variance /= (returns.size() - 1);
double std_dev = std::sqrt(variance);
if (annualize) {
std_dev *= std::sqrt(periods_per_year);
}
return std_dev;
}
double sharpe_ratio(
const std::vector<double>& returns, double risk_free_rate, int periods_per_year) noexcept {
if (returns.size() < 2) return 0.0;
double mean = std::accumulate(returns.begin(), returns.end(), 0.0) / returns.size();
double variance = 0.0;
for (double r : returns) {
double diff = r - mean;
variance += diff * diff;
}
variance /= (returns.size() - 1);
double std_dev = std::sqrt(variance);
if (std_dev == 0.0) return 0.0;
double period_rf = risk_free_rate / periods_per_year;
double excess_return = mean - period_rf;
return (excess_return * periods_per_year) / (std_dev * std::sqrt(periods_per_year));
}
double sortino_ratio(
const std::vector<double>& returns,
double risk_free_rate, double target_return, int periods_per_year) noexcept {
if (returns.size() < 2) return 0.0;
double mean = std::accumulate(returns.begin(), returns.end(), 0.0) / returns.size();
// Downside deviation
double downside_sum = 0.0;
for (double r : returns) {
double diff = std::min(r - target_return, 0.0);
downside_sum += diff * diff;
}
double downside_std = std::sqrt(downside_sum / returns.size());
if (downside_std == 0.0) {
return (mean > risk_free_rate / periods_per_year) ? INFINITY : 0.0;
}
double period_rf = risk_free_rate / periods_per_year;
double excess_return = mean - period_rf;
return (excess_return * periods_per_year) / (downside_std * std::sqrt(periods_per_year));
}
std::tuple<double, size_t, size_t> max_drawdown(
const std::vector<double>& prices) noexcept {
if (prices.size() < 2) return {0.0, 0, 0};
double peak = prices[0];
size_t peak_idx = 0;
double max_dd = 0.0;
size_t max_dd_peak = 0;
size_t max_dd_trough = 0;
for (size_t i = 0; i < prices.size(); ++i) {
if (prices[i] > peak) {
peak = prices[i];
peak_idx = i;
}
double dd = (peak > 0) ? (peak - prices[i]) / peak : 0.0;
if (dd > max_dd) {
max_dd = dd;
max_dd_peak = peak_idx;
max_dd_trough = i;
}
}
return {max_dd, max_dd_peak, max_dd_trough};
}
double var(
const std::vector<double>& returns, double confidence, bool parametric) noexcept {
if (returns.size() < 10) return 0.0;
if (!parametric) {
// Historical VaR
std::vector<double> sorted = returns;
std::sort(sorted.begin(), sorted.end());
size_t idx = static_cast<size_t>(sorted.size() * (1.0 - confidence));
return -sorted[idx];
}
// Parametric VaR (assumes normal)
double mean = std::accumulate(returns.begin(), returns.end(), 0.0) / returns.size();
double variance = 0.0;
for (double r : returns) {
double diff = r - mean;
variance += diff * diff;
}
variance /= (returns.size() - 1);
double std_dev = std::sqrt(variance);
// Z-score approximation
double z = (confidence == 0.95) ? -1.645 : -2.326;
return -(mean + z * std_dev);
}
double cvar(
const std::vector<double>& returns, double confidence) noexcept {
if (returns.size() < 10) return 0.0;
double var_value = var(returns, confidence, false);
// Average of returns worse than VaR
std::vector<double> tail;
for (double r : returns) {
if (r <= -var_value) {
tail.push_back(r);
}
}
if (tail.empty()) return var_value;
return -std::accumulate(tail.begin(), tail.end(), 0.0) / tail.size();
}
// =============================================================================
// SIMD Operations (AVX2)
// =============================================================================
#ifdef LX_SIMD_AVX2
void black_scholes_batch(
const double* S, const double* K, const double* T,
const double* r, const double* sigma,
double* prices, size_t count, bool is_call) noexcept {
// Process 4 at a time with AVX2
size_t i = 0;
for (; i + 4 <= count; i += 4) {
__m256d vS = _mm256_loadu_pd(&S[i]);
__m256d vK = _mm256_loadu_pd(&K[i]);
__m256d vT = _mm256_loadu_pd(&T[i]);
__m256d vr = _mm256_loadu_pd(&r[i]);
__m256d vsigma = _mm256_loadu_pd(&sigma[i]);
// sqrt(T)
__m256d sqrt_T = _mm256_sqrt_pd(vT);
// d1 = (log(S/K) + (r + 0.5*sigma^2)*T) / (sigma*sqrt(T))
__m256d log_SK = _mm256_div_pd(vS, vK);
// Note: Would need log intrinsics or loop here
// Simplified: fall back to scalar for now
for (size_t j = 0; j < 4; ++j) {
prices[i + j] = black_scholes(S[i+j], K[i+j], T[i+j], r[i+j], sigma[i+j], is_call);
}
}
// Handle remainder
for (; i < count; ++i) {
prices[i] = black_scholes(S[i], K[i], T[i], r[i], sigma[i], is_call);
}
}
double sum_avx2(const double* data, size_t count) noexcept {
__m256d sum = _mm256_setzero_pd();
size_t i = 0;
for (; i + 4 <= count; i += 4) {
__m256d v = _mm256_loadu_pd(&data[i]);
sum = _mm256_add_pd(sum, v);
}
// Horizontal sum
double result[4];
_mm256_storeu_pd(result, sum);
double total = result[0] + result[1] + result[2] + result[3];
// Remainder
for (; i < count; ++i) {
total += data[i];
}
return total;
}
double mean_avx2(const double* data, size_t count) noexcept {
if (count == 0) return 0.0;
return sum_avx2(data, count) / count;
}
double variance_avx2(const double* data, size_t count, double mean) noexcept {
if (count < 2) return 0.0;
__m256d vmean = _mm256_set1_pd(mean);
__m256d sum_sq = _mm256_setzero_pd();
size_t i = 0;
for (; i + 4 <= count; i += 4) {
__m256d v = _mm256_loadu_pd(&data[i]);
__m256d diff = _mm256_sub_pd(v, vmean);
sum_sq = _mm256_fmadd_pd(diff, diff, sum_sq);
}
double result[4];
_mm256_storeu_pd(result, sum_sq);
double total = result[0] + result[1] + result[2] + result[3];
// Remainder
for (; i < count; ++i) {
double diff = data[i] - mean;
total += diff * diff;
}
return total / (count - 1);
}
#endif // LX_SIMD_AVX2
// =============================================================================
// Statistical Utilities
// =============================================================================
std::vector<double> calculate_returns(const std::vector<double>& prices) {
if (prices.size() < 2) return {};
std::vector<double> returns;
returns.reserve(prices.size() - 1);
for (size_t i = 1; i < prices.size(); ++i) {
if (prices[i-1] > 0) {
returns.push_back((prices[i] - prices[i-1]) / prices[i-1]);
}
}
return returns;
}
std::vector<double> rolling_mean(const std::vector<double>& data, size_t window) {
if (data.size() < window) return {};
std::vector<double> result;
result.reserve(data.size() - window + 1);
double sum = std::accumulate(data.begin(), data.begin() + window, 0.0);
result.push_back(sum / window);
for (size_t i = window; i < data.size(); ++i) {
sum += data[i] - data[i - window];
result.push_back(sum / window);
}
return result;
}
std::vector<double> rolling_std(const std::vector<double>& data, size_t window) {
if (data.size() < window) return {};
std::vector<double> result;
result.reserve(data.size() - window + 1);
for (size_t i = 0; i <= data.size() - window; ++i) {
double mean = 0.0;
for (size_t j = 0; j < window; ++j) {
mean += data[i + j];
}
mean /= window;
double variance = 0.0;
for (size_t j = 0; j < window; ++j) {
double diff = data[i + j] - mean;
variance += diff * diff;
}
variance /= (window - 1);
result.push_back(std::sqrt(variance));
}
return result;
}
std::vector<double> ema(const std::vector<double>& data, double alpha) {
if (data.empty()) return {};
std::vector<double> result;
result.reserve(data.size());
result.push_back(data[0]);
for (size_t i = 1; i < data.size(); ++i) {
result.push_back(alpha * data[i] + (1.0 - alpha) * result.back());
}
return result;
}
double correlation(const std::vector<double>& x, const std::vector<double>& y) noexcept {
if (x.size() != y.size() || x.size() < 2) return 0.0;
size_t n = x.size();
double mean_x = std::accumulate(x.begin(), x.end(), 0.0) / n;
double mean_y = std::accumulate(y.begin(), y.end(), 0.0) / n;
double cov = 0.0, var_x = 0.0, var_y = 0.0;
for (size_t i = 0; i < n; ++i) {
double dx = x[i] - mean_x;
double dy = y[i] - mean_y;
cov += dx * dy;
var_x += dx * dx;
var_y += dy * dy;
}
double denom = std::sqrt(var_x * var_y);
return (denom > 0) ? cov / denom : 0.0;
}
double covariance(const std::vector<double>& x, const std::vector<double>& y) noexcept {
if (x.size() != y.size() || x.size() < 2) return 0.0;
size_t n = x.size();
double mean_x = std::accumulate(x.begin(), x.end(), 0.0) / n;
double mean_y = std::accumulate(y.begin(), y.end(), 0.0) / n;
double cov = 0.0;
for (size_t i = 0; i < n; ++i) {
cov += (x[i] - mean_x) * (y[i] - mean_y);
}
return cov / (n - 1);
}
double beta(
const std::vector<double>& asset_returns,
const std::vector<double>& market_returns) noexcept {
double cov = covariance(asset_returns, market_returns);
double mean_m = std::accumulate(market_returns.begin(), market_returns.end(), 0.0) /
market_returns.size();
double var_m = 0.0;
for (double r : market_returns) {
double diff = r - mean_m;
var_m += diff * diff;
}
var_m /= (market_returns.size() - 1);
return (var_m > 0) ? cov / var_m : 0.0;
}
} // namespace lx::trading::math
-750
View File
@@ -1,750 +0,0 @@
// LX Trading SDK - Native Adapters Implementation
#include <lx/trading/adapters/native.hpp>
#include <lx/trading/orderbook.hpp>
#include <cpr/cpr.h>
#include <nlohmann/json.hpp>
namespace lx::trading {
using json = nlohmann::json;
// HTTP client wrapper
class HttpClient {
public:
explicit HttpClient(const std::string& base_url) : base_url_(base_url) {}
json get(const std::string& path,
const std::optional<std::string>& api_key = std::nullopt) {
cpr::Header headers;
if (api_key) {
headers["X-API-KEY"] = *api_key;
headers["X-TIMESTAMP"] = std::to_string(now_ms());
}
auto response = cpr::Get(
cpr::Url{base_url_ + path},
headers);
if (response.status_code != 200) {
throw AdapterError("HTTP " + std::to_string(response.status_code) +
": " + response.text);
}
return json::parse(response.text);
}
json post(const std::string& path,
const json& body,
const std::optional<std::string>& api_key = std::nullopt) {
cpr::Header headers{{"Content-Type", "application/json"}};
if (api_key) {
headers["X-API-KEY"] = *api_key;
headers["X-TIMESTAMP"] = std::to_string(now_ms());
}
auto response = cpr::Post(
cpr::Url{base_url_ + path},
headers,
cpr::Body{body.dump()});
if (response.status_code != 200 && response.status_code != 201) {
throw AdapterError("HTTP " + std::to_string(response.status_code) +
": " + response.text);
}
return json::parse(response.text);
}
json del(const std::string& path,
const json& body = {},
const std::optional<std::string>& api_key = std::nullopt) {
cpr::Header headers{{"Content-Type", "application/json"}};
if (api_key) {
headers["X-API-KEY"] = *api_key;
headers["X-TIMESTAMP"] = std::to_string(now_ms());
}
auto response = cpr::Delete(
cpr::Url{base_url_ + path},
headers,
cpr::Body{body.dump()});
if (response.status_code != 200) {
throw AdapterError("HTTP " + std::to_string(response.status_code) +
": " + response.text);
}
return json::parse(response.text);
}
private:
std::string base_url_;
};
// =============================================================================
// LxDexAdapter Implementation
// =============================================================================
LxDexAdapter::LxDexAdapter(std::string_view name, const NativeVenueConfig& config)
: name_(name), config_(config), capabilities_(VenueCapabilities::clob()) {
http_ = std::make_unique<HttpClient>(config.api_url);
}
LxDexAdapter::~LxDexAdapter() = default;
void LxDexAdapter::update_latency(int64_t start_ns) {
int64_t elapsed = now_ns() - start_ns;
latency_.store(static_cast<int>(elapsed / 1000000), std::memory_order_release);
}
std::future<void> LxDexAdapter::connect() {
return std::async(std::launch::async, [this]() {
auto start = now_ns();
http_->get("/api/v1/health");
update_latency(start);
connected_.store(true, std::memory_order_release);
});
}
std::future<void> LxDexAdapter::disconnect() {
return std::async(std::launch::async, [this]() {
connected_.store(false, std::memory_order_release);
});
}
std::future<std::vector<MarketInfo>> LxDexAdapter::get_markets() {
return std::async(std::launch::async, [this]() {
auto start = now_ns();
auto data = http_->get("/api/v1/markets", config_.api_key);
update_latency(start);
std::vector<MarketInfo> markets;
for (const auto& m : data) {
MarketInfo info;
info.symbol = m["symbol"];
info.base = m["base"];
info.quote = m["quote"];
info.price_precision = m.value("pricePrecision", 8);
info.quantity_precision = m.value("quantityPrecision", 8);
info.min_quantity = Decimal::from_string(m.value("minQuantity", "0"));
if (m.contains("maxQuantity") && !m["maxQuantity"].is_null()) {
info.max_quantity = Decimal::from_string(m["maxQuantity"].get<std::string>());
}
if (m.contains("minNotional") && !m["minNotional"].is_null()) {
info.min_notional = Decimal::from_string(m["minNotional"].get<std::string>());
}
info.tick_size = Decimal::from_string(m.value("tickSize", "0.00000001"));
info.lot_size = Decimal::from_string(m.value("lotSize", "0.00000001"));
markets.push_back(std::move(info));
}
return markets;
});
}
std::future<Ticker> LxDexAdapter::get_ticker(const std::string& symbol) {
return std::async(std::launch::async, [this, symbol]() {
auto start = now_ns();
auto data = http_->get("/api/v1/ticker/" + symbol, config_.api_key);
update_latency(start);
Ticker ticker;
ticker.symbol = data.value("symbol", symbol);
ticker.venue = name_;
if (data.contains("bid") && !data["bid"].is_null())
ticker.bid = Decimal::from_string(data["bid"].get<std::string>());
if (data.contains("ask") && !data["ask"].is_null())
ticker.ask = Decimal::from_string(data["ask"].get<std::string>());
if (data.contains("last") && !data["last"].is_null())
ticker.last = Decimal::from_string(data["last"].get<std::string>());
if (data.contains("volume24h") && !data["volume24h"].is_null())
ticker.volume_24h = Decimal::from_string(data["volume24h"].get<std::string>());
ticker.timestamp = data.value("timestamp", now_ms());
return ticker;
});
}
std::future<std::unique_ptr<Orderbook>> LxDexAdapter::get_orderbook(
const std::string& symbol, std::optional<int> depth) {
return std::async(std::launch::async, [this, symbol, depth]() {
auto start = now_ns();
std::string path = "/api/v1/orderbook/" + symbol;
if (depth) path += "?depth=" + std::to_string(*depth);
auto data = http_->get(path, config_.api_key);
update_latency(start);
auto book = std::make_unique<Orderbook>(symbol, name_);
for (const auto& bid : data["bids"]) {
book->add_bid(
Decimal::from_string(bid[0].get<std::string>()),
Decimal::from_string(bid[1].get<std::string>()));
}
for (const auto& ask : data["asks"]) {
book->add_ask(
Decimal::from_string(ask[0].get<std::string>()),
Decimal::from_string(ask[1].get<std::string>()));
}
book->sort();
return book;
});
}
std::future<std::vector<Trade>> LxDexAdapter::get_trades(
const std::string& symbol, std::optional<int> limit) {
return std::async(std::launch::async, [this, symbol, limit]() {
auto start = now_ns();
std::string path = "/api/v1/trades/" + symbol;
if (limit) path += "?limit=" + std::to_string(*limit);
auto data = http_->get(path, config_.api_key);
update_latency(start);
std::vector<Trade> trades;
for (const auto& t : data) {
Trade trade;
trade.trade_id = t["id"];
trade.order_id = t.value("orderId", "");
trade.symbol = symbol;
trade.venue = name_;
trade.side = (t["side"] == "buy") ? Side::Buy : Side::Sell;
trade.price = Decimal::from_string(t["price"].get<std::string>());
trade.quantity = Decimal::from_string(t["quantity"].get<std::string>());
trade.fee.asset = t.value("feeAsset", "");
trade.fee.amount = Decimal::from_string(t.value("feeAmount", "0"));
trade.timestamp = t["timestamp"];
trade.is_maker = t.value("isMaker", false);
trades.push_back(std::move(trade));
}
return trades;
});
}
std::future<std::vector<Balance>> LxDexAdapter::get_balances() {
return std::async(std::launch::async, [this]() {
auto start = now_ns();
auto data = http_->get("/api/v1/account/balances", config_.api_key);
update_latency(start);
std::vector<Balance> balances;
for (const auto& b : data) {
Balance bal;
bal.asset = b["asset"];
bal.venue = name_;
bal.free = Decimal::from_string(b["free"].get<std::string>());
bal.locked = Decimal::from_string(b["locked"].get<std::string>());
balances.push_back(std::move(bal));
}
return balances;
});
}
std::future<Balance> LxDexAdapter::get_balance(const std::string& asset) {
return std::async(std::launch::async, [this, asset]() {
auto start = now_ns();
auto data = http_->get("/api/v1/account/balance/" + asset, config_.api_key);
update_latency(start);
Balance bal;
bal.asset = data["asset"];
bal.venue = name_;
bal.free = Decimal::from_string(data["free"].get<std::string>());
bal.locked = Decimal::from_string(data["locked"].get<std::string>());
return bal;
});
}
std::future<std::vector<Order>> LxDexAdapter::get_open_orders(
const std::optional<std::string>& symbol) {
return std::async(std::launch::async, [this, symbol]() {
auto start = now_ns();
std::string path = "/api/v1/orders?status=open";
if (symbol) path += "&symbol=" + *symbol;
auto data = http_->get(path, config_.api_key);
update_latency(start);
std::vector<Order> orders;
for (const auto& o : data) {
orders.push_back(convert_order(o));
}
return orders;
});
}
Order LxDexAdapter::convert_order(const json& o) {
Order order;
order.order_id = o["orderId"];
order.client_order_id = o.value("clientOrderId", "");
order.symbol = o["symbol"];
order.venue = name_;
order.side = (o["side"] == "buy") ? Side::Buy : Side::Sell;
std::string type_str = o.value("type", "limit");
if (type_str == "market") order.order_type = OrderType::Market;
else if (type_str == "limit") order.order_type = OrderType::Limit;
else if (type_str == "stop_loss") order.order_type = OrderType::StopLoss;
else if (type_str == "stop_loss_limit") order.order_type = OrderType::StopLossLimit;
std::string status_str = o.value("status", "open");
if (status_str == "pending") order.status = OrderStatus::Pending;
else if (status_str == "open") order.status = OrderStatus::Open;
else if (status_str == "partially_filled") order.status = OrderStatus::PartiallyFilled;
else if (status_str == "filled") order.status = OrderStatus::Filled;
else if (status_str == "cancelled") order.status = OrderStatus::Cancelled;
else if (status_str == "rejected") order.status = OrderStatus::Rejected;
else if (status_str == "expired") order.status = OrderStatus::Expired;
order.quantity = Decimal::from_string(o.value("quantity", "0"));
order.filled_quantity = Decimal::from_string(o.value("filledQuantity", "0"));
order.remaining_quantity = order.quantity - order.filled_quantity;
if (o.contains("price") && !o["price"].is_null())
order.price = Decimal::from_string(o["price"].get<std::string>());
if (o.contains("averagePrice") && !o["averagePrice"].is_null())
order.average_price = Decimal::from_string(o["averagePrice"].get<std::string>());
order.created_at = o.value("createdAt", int64_t(0));
order.updated_at = o.value("updatedAt", int64_t(0));
return order;
}
std::future<Order> LxDexAdapter::place_order(const OrderRequest& request) {
return std::async(std::launch::async, [this, request]() -> Order {
auto start = now_ns();
json body = {
{"clientOrderId", request.client_order_id},
{"symbol", request.symbol},
{"side", to_string(request.side)},
{"type", to_string(request.order_type)},
{"quantity", request.quantity.to_string()},
{"timeInForce", to_string(request.time_in_force)}
};
if (request.price) {
body["price"] = request.price->to_string();
}
auto data = http_->post("/api/v1/orders", body, config_.api_key);
update_latency(start);
return convert_order(data);
});
}
std::future<Order> LxDexAdapter::cancel_order(
const std::string& order_id, const std::string& symbol) {
return std::async(std::launch::async, [this, order_id, symbol]() -> Order {
auto start = now_ns();
json body = {{"symbol", symbol}};
auto data = http_->del("/api/v1/orders/" + order_id, body, config_.api_key);
update_latency(start);
return convert_order(data);
});
}
std::future<std::vector<Order>> LxDexAdapter::cancel_all_orders(
const std::optional<std::string>& symbol) {
return std::async(std::launch::async, [this, symbol]() {
auto start = now_ns();
json body = {};
if (symbol) body["symbol"] = *symbol;
auto data = http_->del("/api/v1/orders/all", body, config_.api_key);
update_latency(start);
std::vector<Order> orders;
for (const auto& o : data) {
orders.push_back(convert_order(o));
}
return orders;
});
}
// =============================================================================
// LxAmmAdapter Implementation
// =============================================================================
LxAmmAdapter::LxAmmAdapter(std::string_view name, const NativeVenueConfig& config)
: name_(name), config_(config), capabilities_(VenueCapabilities::amm()) {
http_ = std::make_unique<HttpClient>(config.api_url);
}
LxAmmAdapter::~LxAmmAdapter() = default;
void LxAmmAdapter::update_latency(int64_t start_ns) {
int64_t elapsed = now_ns() - start_ns;
latency_.store(static_cast<int>(elapsed / 1000000), std::memory_order_release);
}
std::future<void> LxAmmAdapter::connect() {
return std::async(std::launch::async, [this]() {
connected_.store(true, std::memory_order_release);
});
}
std::future<void> LxAmmAdapter::disconnect() {
return std::async(std::launch::async, [this]() {
connected_.store(false, std::memory_order_release);
});
}
std::future<std::vector<MarketInfo>> LxAmmAdapter::get_markets() {
return std::async(std::launch::async, [this]() {
auto start = now_ns();
auto data = http_->get("/api/v1/amm/pools");
update_latency(start);
std::vector<MarketInfo> markets;
for (const auto& p : data) {
MarketInfo info;
std::string base = p["baseToken"];
std::string quote = p["quoteToken"];
info.symbol = base + "-" + quote;
info.base = base;
info.quote = quote;
info.price_precision = 8;
info.quantity_precision = 8;
info.tick_size = Decimal::from_double(0.00000001);
info.lot_size = Decimal::from_double(0.00000001);
markets.push_back(std::move(info));
}
return markets;
});
}
std::future<Ticker> LxAmmAdapter::get_ticker(const std::string& symbol) {
return std::async(std::launch::async, [this, symbol]() {
auto pair = TradingPair::from_symbol(symbol);
if (!pair) throw AdapterError("Invalid symbol: " + symbol);
std::string base(pair->base.data());
std::string quote(pair->quote.data());
auto start = now_ns();
auto data = http_->get("/api/v1/amm/price/" + base + "/" + quote);
update_latency(start);
Decimal price = Decimal::from_string(data.value("price", "0"));
Ticker ticker;
ticker.symbol = symbol;
ticker.venue = name_;
ticker.bid = price;
ticker.ask = price;
ticker.last = price;
ticker.timestamp = now_ms();
return ticker;
});
}
std::future<std::unique_ptr<Orderbook>> LxAmmAdapter::get_orderbook(
const std::string& symbol, std::optional<int> depth) {
(void)symbol; (void)depth;
return std::async(std::launch::deferred, []() -> std::unique_ptr<Orderbook> {
throw AdapterError("AMM does not have orderbook");
});
}
std::future<std::vector<Trade>> LxAmmAdapter::get_trades(
const std::string& symbol, std::optional<int> limit) {
return std::async(std::launch::async, [this, symbol, limit]() {
auto pair = TradingPair::from_symbol(symbol);
if (!pair) return std::vector<Trade>{};
std::string base(pair->base.data());
std::string quote(pair->quote.data());
std::string path = "/api/v1/amm/swaps/" + base + "/" + quote;
if (limit) path += "?limit=" + std::to_string(*limit);
auto start = now_ns();
auto data = http_->get(path);
update_latency(start);
std::vector<Trade> trades;
for (const auto& t : data) {
Trade trade;
trade.trade_id = t["txHash"];
trade.order_id = t["txHash"];
trade.symbol = symbol;
trade.venue = name_;
trade.side = (t.value("side", "buy") == "buy") ? Side::Buy : Side::Sell;
trade.price = Decimal::from_string(t["price"].get<std::string>());
trade.quantity = Decimal::from_string(t["amount"].get<std::string>());
trade.fee.amount = Decimal::from_string(t.value("fee", "0"));
trade.timestamp = t["timestamp"];
trade.is_maker = false;
trades.push_back(std::move(trade));
}
return trades;
});
}
std::future<std::vector<Balance>> LxAmmAdapter::get_balances() {
return std::async(std::launch::async, [this]() {
auto start = now_ns();
auto data = http_->get("/api/v1/account/balances");
update_latency(start);
std::vector<Balance> balances;
for (const auto& b : data) {
Balance bal;
bal.asset = b["asset"];
bal.venue = name_;
bal.free = Decimal::from_string(b["free"].get<std::string>());
bal.locked = Decimal::from_string(b.value("locked", "0"));
balances.push_back(std::move(bal));
}
return balances;
});
}
std::future<Balance> LxAmmAdapter::get_balance(const std::string& asset) {
return std::async(std::launch::async, [this, asset]() {
auto start = now_ns();
auto data = http_->get("/api/v1/account/balance/" + asset);
update_latency(start);
Balance bal;
bal.asset = data["asset"];
bal.venue = name_;
bal.free = Decimal::from_string(data["free"].get<std::string>());
bal.locked = Decimal::from_string(data.value("locked", "0"));
return bal;
});
}
std::future<std::vector<Order>> LxAmmAdapter::get_open_orders(
const std::optional<std::string>& symbol) {
(void)symbol;
return std::async(std::launch::deferred, []() { return std::vector<Order>{}; });
}
std::future<Order> LxAmmAdapter::place_order(const OrderRequest& request) {
return std::async(std::launch::async, [this, request]() {
auto pair = TradingPair::from_symbol(request.symbol);
if (!pair) throw AdapterError("Invalid symbol: " + request.symbol);
std::string base(pair->base.data());
std::string quote(pair->quote.data());
auto trade = execute_swap(
base, quote, request.quantity,
request.side == Side::Buy,
Decimal::from_double(0.01)).get();
Order order;
order.order_id = trade.trade_id;
order.client_order_id = request.client_order_id;
order.symbol = request.symbol;
order.venue = name_;
order.side = request.side;
order.order_type = OrderType::Market;
order.status = OrderStatus::Filled;
order.quantity = request.quantity;
order.filled_quantity = trade.quantity;
order.remaining_quantity = Decimal::zero();
order.price = trade.price;
order.average_price = trade.price;
order.created_at = trade.timestamp;
order.updated_at = trade.timestamp;
order.fees.push_back(trade.fee);
return order;
});
}
std::future<Order> LxAmmAdapter::cancel_order(
const std::string& order_id, const std::string& symbol) {
(void)order_id; (void)symbol;
return std::async(std::launch::deferred, []() -> Order {
throw AdapterError("AMM swaps cannot be cancelled");
});
}
std::future<std::vector<Order>> LxAmmAdapter::cancel_all_orders(
const std::optional<std::string>& symbol) {
(void)symbol;
return std::async(std::launch::deferred, []() { return std::vector<Order>{}; });
}
std::future<SwapQuote> LxAmmAdapter::get_swap_quote(
const std::string& base_token,
const std::string& quote_token,
Decimal amount,
bool is_buy) {
return std::async(std::launch::async, [this, base_token, quote_token, amount, is_buy]() {
auto start = now_ns();
json body = {
{"baseToken", base_token},
{"quoteToken", quote_token},
{"amount", amount.to_string()},
{"side", is_buy ? "buy" : "sell"}
};
auto data = http_->post("/api/v1/amm/quote", body);
update_latency(start);
SwapQuote quote;
quote.base_token = base_token;
quote.quote_token = quote_token;
quote.input_amount = amount;
quote.output_amount = Decimal::from_string(data["outputAmount"].get<std::string>());
quote.price = Decimal::from_string(data["price"].get<std::string>());
quote.price_impact = Decimal::from_string(data.value("priceImpact", "0"));
quote.fee = Decimal::from_string(data.value("fee", "0"));
quote.expires_at = now_ms() + 60000;
return quote;
});
}
std::future<Trade> LxAmmAdapter::execute_swap(
const std::string& base_token,
const std::string& quote_token,
Decimal amount,
bool is_buy,
Decimal slippage) {
return std::async(std::launch::async,
[this, base_token, quote_token, amount, is_buy, slippage]() {
auto start = now_ns();
json body = {
{"baseToken", base_token},
{"quoteToken", quote_token},
{"amount", amount.to_string()},
{"side", is_buy ? "buy" : "sell"},
{"slippage", slippage.to_string()}
};
auto data = http_->post("/api/v1/amm/swap", body);
update_latency(start);
Trade trade;
trade.trade_id = data["txHash"];
trade.order_id = data["txHash"];
trade.symbol = base_token + "-" + quote_token;
trade.venue = name_;
trade.side = is_buy ? Side::Buy : Side::Sell;
trade.price = Decimal::from_string(data["price"].get<std::string>());
trade.quantity = amount;
trade.fee.amount = Decimal::from_string(data.value("fee", "0"));
trade.timestamp = now_ms();
trade.is_maker = false;
return trade;
});
}
std::future<PoolInfo> LxAmmAdapter::get_pool_info(
const std::string& base_token,
const std::string& quote_token) {
return std::async(std::launch::async, [this, base_token, quote_token]() {
auto start = now_ns();
auto data = http_->get("/api/v1/amm/pool/" + base_token + "/" + quote_token);
update_latency(start);
PoolInfo info;
info.address = data["address"];
info.base_token = base_token;
info.quote_token = quote_token;
info.base_reserve = Decimal::from_string(data["baseReserve"].get<std::string>());
info.quote_reserve = Decimal::from_string(data["quoteReserve"].get<std::string>());
info.total_liquidity = Decimal::from_string(data.value("totalLiquidity", "0"));
info.fee_rate = Decimal::from_string(data.value("feeRate", "0.003"));
if (data.contains("apy") && !data["apy"].is_null()) {
info.apy = Decimal::from_string(data["apy"].get<std::string>());
}
return info;
});
}
std::future<LiquidityResult> LxAmmAdapter::add_liquidity(
const std::string& base_token,
const std::string& quote_token,
Decimal base_amount,
Decimal quote_amount,
Decimal slippage) {
return std::async(std::launch::async,
[this, base_token, quote_token, base_amount, quote_amount, slippage]() {
auto start = now_ns();
json body = {
{"baseToken", base_token},
{"quoteToken", quote_token},
{"baseAmount", base_amount.to_string()},
{"quoteAmount", quote_amount.to_string()},
{"slippage", slippage.to_string()}
};
auto data = http_->post("/api/v1/amm/liquidity/add", body);
update_latency(start);
LiquidityResult result;
result.tx_hash = data["txHash"];
result.pool_address = data.value("poolAddress", "");
result.base_amount = base_amount;
result.quote_amount = quote_amount;
result.lp_tokens = Decimal::from_string(data.value("lpTokens", "0"));
result.share_percent = Decimal::from_string(data.value("sharePercent", "0"));
return result;
});
}
std::future<LiquidityResult> LxAmmAdapter::remove_liquidity(
const std::string& pool_address,
Decimal liquidity_amount,
Decimal slippage) {
return std::async(std::launch::async,
[this, pool_address, liquidity_amount, slippage]() {
auto start = now_ns();
json body = {
{"poolAddress", pool_address},
{"liquidity", liquidity_amount.to_string()},
{"slippage", slippage.to_string()}
};
auto data = http_->post("/api/v1/amm/liquidity/remove", body);
update_latency(start);
LiquidityResult result;
result.tx_hash = data["txHash"];
result.pool_address = pool_address;
result.base_amount = Decimal::from_string(data.value("baseAmount", "0"));
result.quote_amount = Decimal::from_string(data.value("quoteAmount", "0"));
result.lp_tokens = liquidity_amount;
return result;
});
}
std::future<std::vector<LpPosition>> LxAmmAdapter::get_lp_positions() {
return std::async(std::launch::async, [this]() {
auto start = now_ns();
auto data = http_->get("/api/v1/amm/positions");
update_latency(start);
std::vector<LpPosition> positions;
for (const auto& p : data) {
LpPosition pos;
pos.pool_address = p["poolAddress"];
pos.base_token = p["baseToken"];
pos.quote_token = p["quoteToken"];
pos.lp_tokens = Decimal::from_string(p.value("lpTokens", "0"));
pos.base_amount = Decimal::from_string(p["baseAmount"].get<std::string>());
pos.quote_amount = Decimal::from_string(p["quoteAmount"].get<std::string>());
pos.share_percent = Decimal::from_string(p.value("sharePercent", "0"));
if (p.contains("unrealizedPnl") && !p["unrealizedPnl"].is_null()) {
pos.unrealized_pnl = Decimal::from_string(p["unrealizedPnl"].get<std::string>());
}
positions.push_back(std::move(pos));
}
return positions;
});
}
} // namespace lx::trading
-10
View File
@@ -1,10 +0,0 @@
// LX Trading SDK - Orderbook Implementation
#include <lx/trading/orderbook.hpp>
namespace lx::trading {
// Orderbook and AggregatedOrderbook implementations are header-only
// This file exists for potential future optimizations
} // namespace lx::trading
-181
View File
@@ -1,181 +0,0 @@
// LX Trading SDK - Risk Manager Implementation
#include <lx/trading/risk.hpp>
namespace lx::trading {
RiskManager::RiskManager(const RiskConfig& config) : config_(config) {}
void RiskManager::validate_order(const OrderRequest& request) {
if (!config_.enabled) return;
if (kill_switch_.load(std::memory_order_acquire)) {
throw RiskError("Kill switch is active");
}
// Check order size
if (config_.max_order_size.is_positive() &&
request.quantity > config_.max_order_size) {
throw RiskError(
"Order size " + request.quantity.to_string() +
" exceeds max " + config_.max_order_size.to_string());
}
// Check position limit
auto pair = TradingPair::from_symbol(request.symbol);
if (pair) {
std::string base(pair->base.data());
Decimal current;
{
std::shared_lock lock(position_mutex_);
auto it = positions_.find(base);
if (it != positions_.end()) {
current = it->second;
}
}
Decimal new_position = (request.side == Side::Buy)
? current + request.quantity
: current - request.quantity;
// Asset-specific limit
auto limit_it = config_.position_limits.find(base);
if (limit_it != config_.position_limits.end()) {
if (new_position.abs() > limit_it->second) {
throw RiskError(
"Position limit exceeded for " + base + ": " +
current.to_string() + " + " + request.quantity.to_string() +
" > " + limit_it->second.to_string());
}
}
// Global position limit
if (config_.max_position_size.is_positive() &&
new_position.abs() > config_.max_position_size) {
throw RiskError(
"Max position size exceeded: " +
new_position.abs().to_string() + " > " +
config_.max_position_size.to_string());
}
}
// Check open orders count
{
std::shared_lock lock(orders_mutex_);
auto it = open_orders_.find(request.symbol);
int count = (it != open_orders_.end()) ? it->second : 0;
if (count >= config_.max_open_orders) {
throw RiskError(
"Max open orders (" + std::to_string(config_.max_open_orders) +
") reached for " + request.symbol);
}
}
// Check daily loss
if (config_.max_daily_loss.is_positive()) {
std::shared_lock lock(pnl_mutex_);
if (daily_pnl_.is_negative() &&
daily_pnl_.abs() > config_.max_daily_loss) {
throw RiskError(
"Daily loss limit exceeded: " +
daily_pnl_.abs().to_string() + " > " +
config_.max_daily_loss.to_string());
}
}
}
void RiskManager::update_position(const std::string& asset, Decimal quantity, Side side) {
std::unique_lock lock(position_mutex_);
auto& pos = positions_[asset];
pos = (side == Side::Buy) ? pos + quantity : pos - quantity;
}
Decimal RiskManager::position(const std::string& asset) const {
std::shared_lock lock(position_mutex_);
auto it = positions_.find(asset);
return (it != positions_.end()) ? it->second : Decimal::zero();
}
std::unordered_map<std::string, Decimal> RiskManager::positions() const {
std::shared_lock lock(position_mutex_);
return positions_;
}
void RiskManager::update_pnl(Decimal pnl) {
std::unique_lock lock(pnl_mutex_);
daily_pnl_ = daily_pnl_ + pnl;
// Auto kill switch
if (config_.kill_switch_enabled &&
config_.max_daily_loss.is_positive() &&
daily_pnl_.is_negative() &&
daily_pnl_.abs() > config_.max_daily_loss) {
kill_switch_.store(true, std::memory_order_release);
}
}
Decimal RiskManager::daily_pnl() const {
std::shared_lock lock(pnl_mutex_);
return daily_pnl_;
}
void RiskManager::reset_daily_pnl() {
std::unique_lock lock(pnl_mutex_);
daily_pnl_ = Decimal::zero();
}
void RiskManager::order_opened(const std::string& symbol) {
std::unique_lock lock(orders_mutex_);
open_orders_[symbol]++;
}
void RiskManager::order_closed(const std::string& symbol) {
std::unique_lock lock(orders_mutex_);
auto it = open_orders_.find(symbol);
if (it != open_orders_.end() && it->second > 0) {
it->second--;
}
}
int RiskManager::open_orders(const std::string& symbol) const {
std::shared_lock lock(orders_mutex_);
auto it = open_orders_.find(symbol);
return (it != open_orders_.end()) ? it->second : 0;
}
bool RiskManager::check_order_size(Decimal quantity) const noexcept {
if (!config_.max_order_size.is_positive()) return true;
return quantity <= config_.max_order_size;
}
bool RiskManager::check_position_limit(
const std::string& asset, Decimal new_position) const noexcept {
auto it = config_.position_limits.find(asset);
if (it != config_.position_limits.end()) {
if (new_position.abs() > it->second) return false;
}
if (config_.max_position_size.is_positive()) {
if (new_position.abs() > config_.max_position_size) return false;
}
return true;
}
bool RiskManager::check_daily_loss() const noexcept {
if (!config_.max_daily_loss.is_positive()) return true;
std::shared_lock lock(pnl_mutex_);
return !daily_pnl_.is_negative() ||
daily_pnl_.abs() <= config_.max_daily_loss;
}
bool RiskManager::check_open_orders(const std::string& symbol) const noexcept {
std::shared_lock lock(orders_mutex_);
auto it = open_orders_.find(symbol);
int count = (it != open_orders_.end()) ? it->second : 0;
return count < config_.max_open_orders;
}
} // namespace lx::trading
-149
View File
@@ -1,149 +0,0 @@
// LX Trading SDK - Types Implementation
#include <lx/trading/types.hpp>
#include <algorithm>
#include <charconv>
#include <sstream>
namespace lx::trading {
// Decimal implementation
Decimal Decimal::from_string(std::string_view s) {
// Find decimal point
auto dot = s.find('.');
if (dot == std::string_view::npos) {
// Integer
int64_t val = 0;
std::from_chars(s.data(), s.data() + s.size(), val);
return Decimal(val * SCALE);
}
// Parse integer and fractional parts
std::string_view int_part = s.substr(0, dot);
std::string_view frac_part = s.substr(dot + 1);
int64_t int_val = 0;
if (!int_part.empty()) {
std::from_chars(int_part.data(), int_part.data() + int_part.size(), int_val);
}
int64_t frac_val = 0;
if (!frac_part.empty()) {
// Pad or truncate to PRECISION digits
std::string frac_str(frac_part);
if (frac_str.size() < PRECISION) {
frac_str.append(PRECISION - frac_str.size(), '0');
} else if (frac_str.size() > PRECISION) {
frac_str = frac_str.substr(0, PRECISION);
}
std::from_chars(frac_str.data(), frac_str.data() + frac_str.size(), frac_val);
}
bool negative = (s[0] == '-');
int64_t result = int_val * SCALE + (negative ? -frac_val : frac_val);
return Decimal(result);
}
std::string Decimal::to_string() const {
int64_t abs_val = value_ < 0 ? -value_ : value_;
int64_t int_part = abs_val / SCALE;
int64_t frac_part = abs_val % SCALE;
std::ostringstream oss;
if (value_ < 0) oss << '-';
oss << int_part << '.';
// Format fractional part with leading zeros
std::string frac_str = std::to_string(frac_part);
oss << std::string(PRECISION - frac_str.size(), '0') << frac_str;
std::string result = oss.str();
// Trim trailing zeros after decimal point
size_t last_non_zero = result.find_last_not_of('0');
if (last_non_zero != std::string::npos && result[last_non_zero] == '.') {
last_non_zero--;
}
result = result.substr(0, last_non_zero + 1);
return result;
}
// TradingPair implementation
std::optional<TradingPair> TradingPair::from_symbol(std::string_view symbol) {
const char separators[] = {'-', '/', '_'};
for (char sep : separators) {
auto pos = symbol.find(sep);
if (pos != std::string_view::npos) {
TradingPair pair;
auto base = symbol.substr(0, pos);
auto quote = symbol.substr(pos + 1);
if (base.size() > 15 || quote.size() > 15) {
return std::nullopt;
}
std::copy(base.begin(), base.end(), pair.base.begin());
std::copy(quote.begin(), quote.end(), pair.quote.begin());
return pair;
}
}
return std::nullopt;
}
std::string TradingPair::to_hummingbot() const {
std::string result;
result.reserve(33);
for (char c : base) {
if (c == 0) break;
result += c;
}
result += '-';
for (char c : quote) {
if (c == 0) break;
result += c;
}
return result;
}
std::string TradingPair::to_ccxt() const {
std::string result;
result.reserve(33);
for (char c : base) {
if (c == 0) break;
result += c;
}
result += '/';
for (char c : quote) {
if (c == 0) break;
result += c;
}
return result;
}
// OrderRequest factory methods
OrderRequest OrderRequest::market(std::string_view symbol, Side side, Decimal quantity) {
OrderRequest req;
req.symbol = std::string(symbol);
req.side = side;
req.order_type = OrderType::Market;
req.quantity = quantity;
req.time_in_force = TimeInForce::IOC;
return req;
}
OrderRequest OrderRequest::limit(std::string_view symbol, Side side, Decimal quantity, Decimal price) {
OrderRequest req;
req.symbol = std::string(symbol);
req.side = side;
req.order_type = OrderType::Limit;
req.quantity = quantity;
req.price = price;
req.time_in_force = TimeInForce::GTC;
return req;
}
} // namespace lx::trading
-238
View File
@@ -1,238 +0,0 @@
// LX Trading SDK - Math Tests
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <lx/trading/math.hpp>
using namespace lx::trading::math;
using Catch::Approx;
TEST_CASE("Black-Scholes pricing", "[math]") {
SECTION("At-the-money call") {
double price = black_scholes(100, 100, 1.0, 0.05, 0.2, true);
REQUIRE(price == Approx(10.45).margin(0.1));
}
SECTION("In-the-money call") {
double price = black_scholes(110, 100, 1.0, 0.05, 0.2, true);
REQUIRE(price == Approx(17.68).margin(0.1));
}
SECTION("Out-of-the-money put") {
double price = black_scholes(110, 100, 1.0, 0.05, 0.2, false);
REQUIRE(price == Approx(2.80).margin(0.1));
}
SECTION("Zero time to expiry") {
double call_price = black_scholes(110, 100, 0, 0.05, 0.2, true);
REQUIRE(call_price == Approx(10.0));
double put_price = black_scholes(90, 100, 0, 0.05, 0.2, false);
REQUIRE(put_price == Approx(10.0));
}
}
TEST_CASE("Greeks calculation", "[math]") {
Greeks g = greeks(100, 100, 1.0, 0.05, 0.2, true);
SECTION("Delta") {
REQUIRE(g.delta == Approx(0.64).margin(0.02));
}
SECTION("Gamma") {
REQUIRE(g.gamma == Approx(0.019).margin(0.002));
}
SECTION("Vega") {
REQUIRE(g.vega == Approx(0.38).margin(0.02));
}
SECTION("Theta (daily)") {
REQUIRE(g.theta < 0); // Time decay
}
}
TEST_CASE("Implied volatility", "[math]") {
double true_vol = 0.25;
double price = black_scholes(100, 100, 0.5, 0.05, true_vol, true);
double iv = implied_volatility(price, 100, 100, 0.5, 0.05, true);
REQUIRE(iv == Approx(true_vol).margin(0.01));
}
TEST_CASE("Constant product AMM", "[math]") {
SECTION("Basic swap") {
auto [out, price] = constant_product_price(1000, 1000, 10, 0.003, true);
REQUIRE(out == Approx(9.88).margin(0.02));
REQUIRE(price == Approx(0.988).margin(0.002));
}
SECTION("Large swap with slippage") {
auto [out, price] = constant_product_price(1000, 1000, 100, 0.003, true);
// Larger trade = more slippage
REQUIRE(out < 100);
REQUIRE(price < 1.0);
}
SECTION("Symmetric reserves") {
auto [out1, p1] = constant_product_price(1000, 1000, 50, 0.003, true);
auto [out2, p2] = constant_product_price(1000, 1000, 50, 0.003, false);
REQUIRE(out1 == Approx(out2).margin(0.01));
}
}
TEST_CASE("Concentrated liquidity", "[math]") {
// Price range 90-110, current sqrt price = 10 (price = 100)
double sqrt_price = 10.0;
double sqrt_lower = std::sqrt(90.0);
double sqrt_upper = std::sqrt(110.0);
double liquidity = 1000.0;
SECTION("Swap within range") {
auto [out, new_sqrt_p, impact] = concentrated_liquidity_price(
liquidity, sqrt_price, sqrt_lower, sqrt_upper, 10, 0.003, true);
REQUIRE(out > 0);
REQUIRE(new_sqrt_p > sqrt_price); // Price increased
REQUIRE(impact >= 0);
}
}
TEST_CASE("Volatility calculation", "[math]") {
std::vector<double> returns = {0.01, -0.02, 0.015, -0.01, 0.02, 0.005};
SECTION("Non-annualized") {
double vol = volatility(returns, false);
REQUIRE(vol > 0);
REQUIRE(vol < 0.1); // Daily returns typically small
}
SECTION("Annualized") {
double vol_daily = volatility(returns, false);
double vol_annual = volatility(returns, true, 252);
REQUIRE(vol_annual == Approx(vol_daily * std::sqrt(252)).margin(0.001));
}
}
TEST_CASE("Sharpe ratio", "[math]") {
std::vector<double> positive_returns = {0.01, 0.02, 0.015, 0.01, 0.02};
std::vector<double> mixed_returns = {0.01, -0.02, 0.015, -0.01, 0.02};
SECTION("Positive returns") {
double sharpe = sharpe_ratio(positive_returns, 0.0);
REQUIRE(sharpe > 0);
}
SECTION("Mixed returns") {
double sharpe = sharpe_ratio(mixed_returns, 0.0);
// Lower Sharpe due to more volatility
REQUIRE(sharpe < sharpe_ratio(positive_returns, 0.0));
}
}
TEST_CASE("Maximum drawdown", "[math]") {
std::vector<double> prices = {100, 110, 105, 95, 90, 100, 85};
auto [dd, peak_idx, trough_idx] = max_drawdown(prices);
// Max DD from 110 to 85 = 22.7%
REQUIRE(dd == Approx(0.227).margin(0.01));
REQUIRE(peak_idx == 1); // 110
REQUIRE(trough_idx == 6); // 85
}
TEST_CASE("Value at Risk", "[math]") {
std::vector<double> returns = {
-0.05, -0.03, -0.02, -0.01, 0.0, 0.01, 0.02, 0.03, 0.04, 0.05,
-0.04, -0.02, -0.01, 0.0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06
};
SECTION("Historical VaR 95%") {
double var95 = var(returns, 0.95, false);
REQUIRE(var95 > 0);
REQUIRE(var95 < 0.1);
}
SECTION("Parametric VaR 95%") {
double var95 = var(returns, 0.95, true);
REQUIRE(var95 > 0);
}
}
TEST_CASE("Conditional VaR", "[math]") {
std::vector<double> returns = {
-0.08, -0.06, -0.05, -0.04, -0.03, -0.02, -0.01, 0.0, 0.01, 0.02,
0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.10, 0.11, 0.12
};
double cvar95 = cvar(returns, 0.95);
double var95 = var(returns, 0.95, false);
// CVaR should be >= VaR
REQUIRE(cvar95 >= var95);
}
TEST_CASE("Statistical utilities", "[math]") {
SECTION("Calculate returns") {
std::vector<double> prices = {100, 105, 102, 110};
auto returns = calculate_returns(prices);
REQUIRE(returns.size() == 3);
REQUIRE(returns[0] == Approx(0.05)); // 5%
REQUIRE(returns[1] == Approx(-0.0286).margin(0.001)); // -2.86%
REQUIRE(returns[2] == Approx(0.0784).margin(0.001)); // 7.84%
}
SECTION("Rolling mean") {
std::vector<double> data = {1, 2, 3, 4, 5, 6, 7};
auto rm = rolling_mean(data, 3);
REQUIRE(rm.size() == 5);
REQUIRE(rm[0] == Approx(2.0)); // (1+2+3)/3
REQUIRE(rm[1] == Approx(3.0)); // (2+3+4)/3
}
SECTION("EMA") {
std::vector<double> data = {10, 12, 11, 13, 12, 14};
auto e = ema(data, 0.3);
REQUIRE(e.size() == 6);
REQUIRE(e[0] == Approx(10.0));
// Each subsequent value is weighted average
}
SECTION("Correlation") {
std::vector<double> x = {1, 2, 3, 4, 5};
std::vector<double> y = {2, 4, 6, 8, 10};
double corr = correlation(x, y);
REQUIRE(corr == Approx(1.0)); // Perfect positive
}
SECTION("Beta") {
std::vector<double> asset = {0.02, 0.03, -0.01, 0.02, 0.01};
std::vector<double> market = {0.01, 0.015, -0.005, 0.01, 0.005};
double b = beta(asset, market);
REQUIRE(b > 1.0); // Asset more volatile than market
}
}
TEST_CASE("Price conversions", "[math]") {
SECTION("Price to sqrt price") {
double sqrt_p = price_to_sqrt_price(100);
REQUIRE(sqrt_p == Approx(10.0));
}
SECTION("Sqrt price to price") {
double p = sqrt_price_to_price(10.0);
REQUIRE(p == Approx(100.0));
}
SECTION("Round trip") {
double original = 12345.67;
double sqrt_p = price_to_sqrt_price(original);
double back = sqrt_price_to_price(sqrt_p);
REQUIRE(back == Approx(original));
}
}
-171
View File
@@ -1,171 +0,0 @@
// LX Trading SDK - Orderbook Tests
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <lx/trading/orderbook.hpp>
using namespace lx::trading;
using Catch::Approx;
TEST_CASE("Orderbook basic operations", "[orderbook]") {
Orderbook book("BTC-USDC", "test_venue");
SECTION("Add and retrieve levels") {
book.add_bid(Decimal::from_double(100.0), Decimal::from_double(1.0));
book.add_bid(Decimal::from_double(99.0), Decimal::from_double(2.0));
book.add_ask(Decimal::from_double(101.0), Decimal::from_double(1.5));
book.add_ask(Decimal::from_double(102.0), Decimal::from_double(2.5));
book.sort();
auto bids = book.bids();
auto asks = book.asks();
REQUIRE(bids.size() == 2);
REQUIRE(asks.size() == 2);
// Bids sorted descending
REQUIRE(bids[0].price.to_double() == Approx(100.0));
REQUIRE(bids[1].price.to_double() == Approx(99.0));
// Asks sorted ascending
REQUIRE(asks[0].price.to_double() == Approx(101.0));
REQUIRE(asks[1].price.to_double() == Approx(102.0));
}
SECTION("Best bid/ask") {
book.add_bid(Decimal::from_double(100.0), Decimal::from_double(1.0));
book.add_ask(Decimal::from_double(101.0), Decimal::from_double(1.0));
book.sort();
REQUIRE(book.best_bid().value().to_double() == Approx(100.0));
REQUIRE(book.best_ask().value().to_double() == Approx(101.0));
}
SECTION("Mid price and spread") {
book.add_bid(Decimal::from_double(100.0), Decimal::from_double(1.0));
book.add_ask(Decimal::from_double(102.0), Decimal::from_double(1.0));
book.sort();
REQUIRE(book.mid_price().value().to_double() == Approx(101.0));
REQUIRE(book.spread().value().to_double() == Approx(2.0));
REQUIRE(book.spread_percent().value().to_double() == Approx(1.98).margin(0.01));
}
}
TEST_CASE("Orderbook VWAP", "[orderbook]") {
Orderbook book("BTC-USDC", "test_venue");
book.add_ask(Decimal::from_double(100.0), Decimal::from_double(1.0));
book.add_ask(Decimal::from_double(101.0), Decimal::from_double(2.0));
book.add_ask(Decimal::from_double(102.0), Decimal::from_double(3.0));
book.sort();
SECTION("VWAP for small amount") {
auto vwap = book.vwap_buy(Decimal::from_double(0.5));
REQUIRE(vwap.has_value());
REQUIRE(vwap->to_double() == Approx(100.0));
}
SECTION("VWAP across multiple levels") {
auto vwap = book.vwap_buy(Decimal::from_double(2.5));
REQUIRE(vwap.has_value());
// (1.0 * 100 + 1.5 * 101) / 2.5 = 100.6
REQUIRE(vwap->to_double() == Approx(100.6));
}
SECTION("VWAP for full book") {
auto vwap = book.vwap_buy(Decimal::from_double(6.0));
REQUIRE(vwap.has_value());
// (1*100 + 2*101 + 3*102) / 6 = 101.333...
REQUIRE(vwap->to_double() == Approx(101.333).margin(0.01));
}
}
TEST_CASE("Orderbook liquidity", "[orderbook]") {
Orderbook book("BTC-USDC", "test_venue");
book.add_bid(Decimal::from_double(100.0), Decimal::from_double(1.0));
book.add_bid(Decimal::from_double(99.0), Decimal::from_double(2.0));
book.add_ask(Decimal::from_double(101.0), Decimal::from_double(1.5));
book.add_ask(Decimal::from_double(102.0), Decimal::from_double(2.5));
book.sort();
SECTION("Bid liquidity") {
// 1.0 * 100 + 2.0 * 99 = 298
REQUIRE(book.bid_liquidity().to_double() == Approx(298.0));
}
SECTION("Ask liquidity") {
// 1.5 * 101 + 2.5 * 102 = 406.5
REQUIRE(book.ask_liquidity().to_double() == Approx(406.5));
}
SECTION("Depth for specific levels") {
REQUIRE(book.bid_depth(1).to_double() == Approx(100.0));
REQUIRE(book.ask_depth(1).to_double() == Approx(151.5));
}
SECTION("Has liquidity") {
REQUIRE(book.has_liquidity(Side::Buy, Decimal::from_double(3.0)));
REQUIRE_FALSE(book.has_liquidity(Side::Buy, Decimal::from_double(10.0)));
}
}
TEST_CASE("AggregatedOrderbook", "[orderbook]") {
AggregatedOrderbook agg("BTC-USDC");
// Create first venue orderbook
Orderbook book1("BTC-USDC", "venue1");
book1.add_bid(Decimal::from_double(100.0), Decimal::from_double(1.0));
book1.add_ask(Decimal::from_double(102.0), Decimal::from_double(1.0));
book1.sort();
// Create second venue orderbook
Orderbook book2("BTC-USDC", "venue2");
book2.add_bid(Decimal::from_double(99.0), Decimal::from_double(2.0));
book2.add_ask(Decimal::from_double(101.0), Decimal::from_double(1.5));
book2.sort();
agg.add_orderbook(book1);
agg.add_orderbook(book2);
SECTION("Best bid across venues") {
auto best = agg.best_bid();
REQUIRE(best.has_value());
auto [price, venue, qty] = *best;
REQUIRE(price.to_double() == Approx(100.0));
REQUIRE(venue == "venue1");
}
SECTION("Best ask across venues") {
auto best = agg.best_ask();
REQUIRE(best.has_value());
auto [price, venue, qty] = *best;
REQUIRE(price.to_double() == Approx(101.0));
REQUIRE(venue == "venue2");
}
SECTION("Aggregated levels") {
auto asks = agg.aggregated_asks();
REQUIRE(asks.size() == 2);
// Sorted by price ascending
REQUIRE(asks[0].price.to_double() == Approx(101.0));
REQUIRE(asks[1].price.to_double() == Approx(102.0));
}
SECTION("Best venue for buying") {
auto best = agg.best_venue_buy(Decimal::from_double(1.0));
REQUIRE(best.has_value());
auto [venue, price] = *best;
REQUIRE(venue == "venue2"); // Lower ask price
REQUIRE(price.to_double() == Approx(101.0));
}
SECTION("Best venue for selling") {
auto best = agg.best_venue_sell(Decimal::from_double(0.5));
REQUIRE(best.has_value());
auto [venue, price] = *best;
REQUIRE(venue == "venue1"); // Higher bid price
REQUIRE(price.to_double() == Approx(100.0));
}
}
-227
View File
@@ -1,227 +0,0 @@
// LX Trading SDK - Risk Manager Tests
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <lx/trading/risk.hpp>
using namespace lx::trading;
using Catch::Approx;
TEST_CASE("RiskManager basic validation", "[risk]") {
RiskConfig config;
config.enabled = true;
config.max_order_size = Decimal::from_double(100.0);
config.max_position_size = Decimal::from_double(1000.0);
config.max_open_orders = 10;
RiskManager rm(config);
SECTION("Valid order passes") {
OrderRequest req = OrderRequest::market("BTC-USDC", Side::Buy,
Decimal::from_double(50.0));
REQUIRE_NOTHROW(rm.validate_order(req));
}
SECTION("Order size exceeded") {
OrderRequest req = OrderRequest::market("BTC-USDC", Side::Buy,
Decimal::from_double(150.0));
REQUIRE_THROWS_AS(rm.validate_order(req), RiskError);
}
}
TEST_CASE("RiskManager position limits", "[risk]") {
RiskConfig config;
config.enabled = true;
config.max_position_size = Decimal::from_double(100.0);
config.position_limits["BTC"] = Decimal::from_double(50.0);
RiskManager rm(config);
SECTION("Within global limit") {
rm.update_position("BTC", Decimal::from_double(30.0), Side::Buy);
REQUIRE(rm.position("BTC").to_double() == Approx(30.0));
OrderRequest req = OrderRequest::market("BTC-USDC", Side::Buy,
Decimal::from_double(10.0));
REQUIRE_NOTHROW(rm.validate_order(req));
}
SECTION("Exceeds asset-specific limit") {
rm.update_position("BTC", Decimal::from_double(45.0), Side::Buy);
OrderRequest req = OrderRequest::market("BTC-USDC", Side::Buy,
Decimal::from_double(10.0));
REQUIRE_THROWS_AS(rm.validate_order(req), RiskError);
}
SECTION("Exceeds global limit") {
rm.update_position("ETH", Decimal::from_double(90.0), Side::Buy);
OrderRequest req = OrderRequest::market("ETH-USDC", Side::Buy,
Decimal::from_double(20.0));
REQUIRE_THROWS_AS(rm.validate_order(req), RiskError);
}
}
TEST_CASE("RiskManager PnL tracking", "[risk]") {
RiskConfig config;
config.enabled = true;
config.max_daily_loss = Decimal::from_double(1000.0);
config.kill_switch_enabled = true;
RiskManager rm(config);
SECTION("Track PnL") {
rm.update_pnl(Decimal::from_double(100.0));
rm.update_pnl(Decimal::from_double(-50.0));
REQUIRE(rm.daily_pnl().to_double() == Approx(50.0));
}
SECTION("Daily loss limit triggers rejection") {
rm.update_pnl(Decimal::from_double(-1001.0));
OrderRequest req = OrderRequest::market("BTC-USDC", Side::Buy,
Decimal::from_double(1.0));
REQUIRE_THROWS_AS(rm.validate_order(req), RiskError);
}
SECTION("Kill switch activation") {
REQUIRE_FALSE(rm.is_killed());
rm.update_pnl(Decimal::from_double(-1001.0));
REQUIRE(rm.is_killed());
}
SECTION("Reset PnL") {
rm.update_pnl(Decimal::from_double(-500.0));
rm.reset_daily_pnl();
REQUIRE(rm.daily_pnl().is_zero());
}
}
TEST_CASE("RiskManager order tracking", "[risk]") {
RiskConfig config;
config.enabled = true;
config.max_open_orders = 5;
RiskManager rm(config);
SECTION("Track open orders") {
rm.order_opened("BTC-USDC");
rm.order_opened("BTC-USDC");
REQUIRE(rm.open_orders("BTC-USDC") == 2);
rm.order_closed("BTC-USDC");
REQUIRE(rm.open_orders("BTC-USDC") == 1);
}
SECTION("Max open orders limit") {
for (int i = 0; i < 5; ++i) {
rm.order_opened("ETH-USDC");
}
OrderRequest req = OrderRequest::market("ETH-USDC", Side::Buy,
Decimal::from_double(1.0));
REQUIRE_THROWS_AS(rm.validate_order(req), RiskError);
}
}
TEST_CASE("RiskManager kill switch", "[risk]") {
RiskConfig config;
config.enabled = true;
RiskManager rm(config);
SECTION("Manual kill") {
REQUIRE_FALSE(rm.is_killed());
rm.kill();
REQUIRE(rm.is_killed());
OrderRequest req = OrderRequest::market("BTC-USDC", Side::Buy,
Decimal::from_double(1.0));
REQUIRE_THROWS_AS(rm.validate_order(req), RiskError);
}
SECTION("Reset kill switch") {
rm.kill();
rm.reset();
REQUIRE_FALSE(rm.is_killed());
}
}
TEST_CASE("RiskManager disabled", "[risk]") {
RiskConfig config;
config.enabled = false;
config.max_order_size = Decimal::from_double(10.0);
RiskManager rm(config);
SECTION("Validation passes when disabled") {
OrderRequest req = OrderRequest::market("BTC-USDC", Side::Buy,
Decimal::from_double(1000.0)); // Way over limit
REQUIRE_NOTHROW(rm.validate_order(req));
}
}
TEST_CASE("RiskManager pre-trade checks", "[risk]") {
RiskConfig config;
config.enabled = true;
config.max_order_size = Decimal::from_double(100.0);
config.max_position_size = Decimal::from_double(500.0);
config.max_daily_loss = Decimal::from_double(1000.0);
config.max_open_orders = 5;
RiskManager rm(config);
SECTION("Check order size") {
REQUIRE(rm.check_order_size(Decimal::from_double(50.0)));
REQUIRE_FALSE(rm.check_order_size(Decimal::from_double(150.0)));
}
SECTION("Check position limit") {
REQUIRE(rm.check_position_limit("BTC", Decimal::from_double(100.0)));
REQUIRE_FALSE(rm.check_position_limit("BTC", Decimal::from_double(600.0)));
}
SECTION("Check daily loss") {
REQUIRE(rm.check_daily_loss());
rm.update_pnl(Decimal::from_double(-1001.0));
REQUIRE_FALSE(rm.check_daily_loss());
}
SECTION("Check open orders") {
REQUIRE(rm.check_open_orders("BTC-USDC"));
for (int i = 0; i < 5; ++i) {
rm.order_opened("BTC-USDC");
}
REQUIRE_FALSE(rm.check_open_orders("BTC-USDC"));
}
}
TEST_CASE("OrderTracker RAII", "[risk]") {
RiskConfig config;
config.enabled = true;
config.max_open_orders = 10;
RiskManager rm(config);
SECTION("Auto decrement on destruction") {
{
OrderTracker tracker(rm, "BTC-USDC");
REQUIRE(rm.open_orders("BTC-USDC") == 1);
}
REQUIRE(rm.open_orders("BTC-USDC") == 0);
}
SECTION("Release prevents decrement") {
{
OrderTracker tracker(rm, "BTC-USDC");
tracker.release();
}
REQUIRE(rm.open_orders("BTC-USDC") == 1);
}
}
-172
View File
@@ -1,172 +0,0 @@
// LX Trading SDK - Types Tests
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <lx/trading/types.hpp>
using namespace lx::trading;
using Catch::Approx;
TEST_CASE("Decimal arithmetic", "[types]") {
SECTION("Basic operations") {
Decimal a = Decimal::from_double(100.5);
Decimal b = Decimal::from_double(50.25);
REQUIRE((a + b).to_double() == Approx(150.75));
REQUIRE((a - b).to_double() == Approx(50.25));
REQUIRE((a * Decimal::from_double(2.0)).to_double() == Approx(201.0));
REQUIRE((a / Decimal::from_double(2.0)).to_double() == Approx(50.25));
}
SECTION("String conversion") {
auto d = Decimal::from_string("123.456");
REQUIRE(d.to_double() == Approx(123.456));
auto d2 = Decimal::from_string("-99.99");
REQUIRE(d2.to_double() == Approx(-99.99));
REQUIRE(d2.is_negative());
}
SECTION("Comparison") {
Decimal a = Decimal::from_double(10.0);
Decimal b = Decimal::from_double(20.0);
REQUIRE(a < b);
REQUIRE(b > a);
REQUIRE(a <= a);
REQUIRE(a == a);
REQUIRE(a != b);
}
SECTION("Zero and one") {
REQUIRE(Decimal::zero().is_zero());
REQUIRE(Decimal::one().to_double() == Approx(1.0));
}
}
TEST_CASE("TradingPair parsing", "[types]") {
SECTION("Hyphen separator") {
auto pair = TradingPair::from_symbol("BTC-USDC");
REQUIRE(pair.has_value());
REQUIRE(std::string(pair->base.data()) == "BTC");
REQUIRE(std::string(pair->quote.data()) == "USDC");
}
SECTION("Slash separator") {
auto pair = TradingPair::from_symbol("ETH/USD");
REQUIRE(pair.has_value());
REQUIRE(std::string(pair->base.data()) == "ETH");
REQUIRE(std::string(pair->quote.data()) == "USD");
}
SECTION("Underscore separator") {
auto pair = TradingPair::from_symbol("LUX_USDT");
REQUIRE(pair.has_value());
REQUIRE(std::string(pair->base.data()) == "LUX");
REQUIRE(std::string(pair->quote.data()) == "USDT");
}
SECTION("Invalid symbol") {
auto pair = TradingPair::from_symbol("INVALID");
REQUIRE_FALSE(pair.has_value());
}
SECTION("Format conversions") {
auto pair = TradingPair::from_symbol("BTC-USDC");
REQUIRE(pair->to_hummingbot() == "BTC-USDC");
REQUIRE(pair->to_ccxt() == "BTC/USDC");
}
}
TEST_CASE("OrderRequest factory methods", "[types]") {
SECTION("Market order") {
auto req = OrderRequest::market("BTC-USDC", Side::Buy, Decimal::from_double(1.5));
REQUIRE(req.symbol == "BTC-USDC");
REQUIRE(req.side == Side::Buy);
REQUIRE(req.order_type == OrderType::Market);
REQUIRE(req.quantity.to_double() == Approx(1.5));
REQUIRE(req.time_in_force == TimeInForce::IOC);
}
SECTION("Limit order") {
auto req = OrderRequest::limit("ETH-USDC", Side::Sell,
Decimal::from_double(10.0), Decimal::from_double(2000.0));
REQUIRE(req.symbol == "ETH-USDC");
REQUIRE(req.side == Side::Sell);
REQUIRE(req.order_type == OrderType::Limit);
REQUIRE(req.price.value().to_double() == Approx(2000.0));
REQUIRE(req.time_in_force == TimeInForce::GTC);
}
SECTION("Builder methods") {
auto req = OrderRequest::market("BTC-USDC", Side::Buy, Decimal::from_double(1.0))
.with_venue("lx_dex")
.with_post_only()
.with_client_id("my-order-123");
REQUIRE(req.venue.value() == "lx_dex");
REQUIRE(req.post_only == true);
REQUIRE(req.time_in_force == TimeInForce::PostOnly);
REQUIRE(req.client_order_id == "my-order-123");
}
}
TEST_CASE("Order status checks", "[types]") {
Order order;
order.quantity = Decimal::from_double(100.0);
order.filled_quantity = Decimal::from_double(50.0);
order.remaining_quantity = Decimal::from_double(50.0);
SECTION("Partially filled") {
order.status = OrderStatus::PartiallyFilled;
REQUIRE(order.is_open());
REQUIRE_FALSE(order.is_done());
REQUIRE(order.fill_percent().to_double() == Approx(50.0));
}
SECTION("Filled") {
order.status = OrderStatus::Filled;
order.filled_quantity = order.quantity;
REQUIRE_FALSE(order.is_open());
REQUIRE(order.is_done());
}
SECTION("Cancelled") {
order.status = OrderStatus::Cancelled;
REQUIRE_FALSE(order.is_open());
REQUIRE(order.is_done());
}
}
TEST_CASE("Ticker calculations", "[types]") {
Ticker ticker;
ticker.bid = Decimal::from_double(100.0);
ticker.ask = Decimal::from_double(101.0);
SECTION("Mid price") {
auto mid = ticker.mid_price();
REQUIRE(mid.has_value());
REQUIRE(mid->to_double() == Approx(100.5));
}
SECTION("Spread") {
auto spread = ticker.spread();
REQUIRE(spread.has_value());
REQUIRE(spread->to_double() == Approx(1.0));
}
SECTION("Spread percent") {
auto spread_pct = ticker.spread_percent();
REQUIRE(spread_pct.has_value());
REQUIRE(spread_pct->to_double() == Approx(1.0));
}
}
TEST_CASE("Enum to_string", "[types]") {
REQUIRE(std::string(to_string(Side::Buy)) == "buy");
REQUIRE(std::string(to_string(Side::Sell)) == "sell");
REQUIRE(std::string(to_string(OrderType::Market)) == "market");
REQUIRE(std::string(to_string(OrderType::Limit)) == "limit");
REQUIRE(std::string(to_string(TimeInForce::GTC)) == "GTC");
REQUIRE(std::string(to_string(OrderStatus::Filled)) == "filled");
}