0% found this document useful (0 votes)
5 views16 pages

Codice

The document describes a complete monitoring system for Polymarket, featuring encrypted keystore management, Level 1 and Level 2 authentication for API access, and automated coverage execution upon market detection. It includes setup instructions, dependencies, and compilation commands, as well as detailed logging and utility functions for handling cryptographic operations. The system is designed to ensure secure and efficient interaction with the Polymarket API while managing market data and trades effectively.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views16 pages

Codice

The document describes a complete monitoring system for Polymarket, featuring encrypted keystore management, Level 1 and Level 2 authentication for API access, and automated coverage execution upon market detection. It includes setup instructions, dependencies, and compilation commands, as well as detailed logging and utility functions for handling cryptographic operations. The system is designed to ensure secure and efficient interaction with the Polymarket API while managing market data and trades effectively.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

/**

* monitor_polymarket.cpp
* ----------------------
* Monitor completo con:
* - Keystore encriptado (AES-256-CBC + PBKDF2 via OpenSSL)
* - Autenticación L1 (EIP-712) para obtener API credentials
* - Autenticación L2 (HMAC-SHA256) para firmar cada orden
* - Ejecución automática de cobertura al detectar mercado
* - Cursor createdAfter, histórico persistente, percentil adaptativo
*
* Dependencias:
* - nlohmann/json [Link]
* - cpr [Link]
* - OpenSSL (libssl-dev en Ubuntu / brew install openssl en macOS)
*
* Compilar:
* g++ -std=c++20 -O2 monitor_polymarket.cpp \
* -lcpr -lcurl -lssl -lcrypto \
* -I/path/to/nlohmann \
* -o monitor
*
* Primer uso (crear keystore):
* ./monitor --create-keystore
*
* Uso normal:
* ./monitor
*/

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <deque>
#include <set>
#include <string>
#include <chrono>
#include <thread>
#include <cmath>
#include <numeric>
#include <algorithm>
#include <stdexcept>
#include <ctime>
#include <iomanip>

#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <nlohmann/[Link]>
#include <cpr/cpr.h>

using json = nlohmann::json;

// ==================== Configuración ====================

constexpr int POLLING_SEC = 30;


constexpr double PAGO_BASE = 100.0; // USDC por mercado — ajustar con
cuidado
constexpr double SPREAD_MIN = 0.03;
constexpr double ARBITRAJE_UMBRAL = 0.98; // suma_precios < esto → arbitraje
(fees 2%)
constexpr int MAX_DIAS_RESOLUCION = 7;
constexpr int RESUMEN_CADA = 10;
constexpr int COLA_ROI_MAX = 100;
constexpr int MIN_MUESTRAS_HIST = 50;
constexpr int PERCENTIL_VOLUMEN = 30;
constexpr double VOLUMEN_MAX_INICIAL = 10'000.0;
constexpr int API_LIMIT = 20;
constexpr double PRICE_SLIPPAGE_MAX = 0.02; // precio live puede subir máx 2% vs
Gamma antes de abortar

const std::string KEYSTORE_FILE = "[Link]";


const std::string CREDS_FILE = "api_creds.json";
const std::string HISTORICO_FILE = "mercados_historial.json";
const std::string LOG_FILE = "[Link]";
const std::string GAMMA_API = "[Link]
const std::string CLOB_API = "[Link]

// ==================== Logging ====================

std::ofstream g_logfile;

std::string timestamp_str() {
auto now = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(now);
std::ostringstream ss;
ss << std::put_time(std::gmtime(&t), "%Y-%m-%d %H:%M:%S UTC");
return [Link]();
}

enum class LogLevel { INFO, WARNING, ERR };

void log_msg(LogLevel level, const std::string& msg) {


const char* tag = (level == LogLevel::WARNING) ? "[WARNING]" :
(level == LogLevel::ERR) ? "[ERROR] " : "[INFO] ";
std::string line = timestamp_str() + " " + tag + " " + msg;
std::cout << line << "\n";
if (g_logfile.is_open()) g_logfile << line << "\n" << std::flush;
}

// ==================== Utilidades de bytes ====================

std::string bytes_to_hex(const std::vector<uint8_t>& b) {


std::ostringstream ss;
ss << std::hex << std::setfill('0');
for (auto c : b) ss << std::setw(2) << (int)c;
return [Link]();
}

std::vector<uint8_t> hex_to_bytes(const std::string& hex) {


std::vector<uint8_t> out;
for (size_t i = 0; i + 1 < [Link](); i += 2)
out.push_back((uint8_t)std::stoi([Link](i, 2), nullptr, 16));
return out;
}

std::string base64_encode(const std::vector<uint8_t>& data) {


static const char* T =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string out;
int val = 0, valb = -6;
for (uint8_t c : data) {
val = (val << 8) + c; valb += 8;
while (valb >= 0) { out.push_back(T[(val >> valb) & 0x3F]); valb -= 6; }
}
if (valb > -6) out.push_back(T[((val << 8) >> (valb + 8)) & 0x3F]);
while ([Link]() % 4) out.push_back('=');
return out;
}

// ==================== Keystore (AES-256-CBC + PBKDF2-SHA256)


====================

json crear_keystore(const std::string& pk_hex, const std::string& passphrase) {


std::vector<uint8_t> salt(32), iv(16);
RAND_bytes([Link](), (int)[Link]());
RAND_bytes([Link](), (int)[Link]());

// Derivar clave
std::vector<uint8_t> dk(32);
PKCS5_PBKDF2_HMAC(passphrase.c_str(), (int)[Link](),
[Link](), (int)[Link](), 262144,
EVP_sha256(), (int)[Link](), [Link]());
// Cifrar
auto pk = hex_to_bytes(pk_hex);
std::vector<uint8_t> ct([Link]() + 16);
int l1 = 0, l2 = 0;
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, [Link](), [Link]());
EVP_EncryptUpdate(ctx, [Link](), &l1, [Link](), (int)[Link]());
EVP_EncryptFinal_ex(ctx, [Link]() + l1, &l2);
EVP_CIPHER_CTX_free(ctx);
[Link](l1 + l2);

// MAC para verificar integridad


std::vector<uint8_t> mac_key([Link]() + 16, [Link]());
unsigned char mac[32]; unsigned int mac_len = 32;
HMAC(EVP_sha256(), mac_key.data(), (int)mac_key.size(),
[Link](), (int)[Link](), mac, &mac_len);

return {
{"version", 1},
{"crypto", {
{"cipher", "aes-256-cbc"},
{"ciphertext", bytes_to_hex(ct)},
{"iv", bytes_to_hex(iv)},
{"kdf", "pbkdf2"},
{"kdfparams", {{"salt", bytes_to_hex(salt)}, {"iterations", 262144}}},
{"mac", bytes_to_hex(std::vector<uint8_t>(mac, mac+32))}
}}
};
}

std::string descifrar_keystore(const json& ks, const std::string& passphrase) {


auto& cr = ks["crypto"];
auto salt = hex_to_bytes(cr["kdfparams"]["salt"].get<std::string>());
auto iv = hex_to_bytes(cr["iv"].get<std::string>());
auto ct = hex_to_bytes(cr["ciphertext"].get<std::string>());
auto mac_ok = hex_to_bytes(cr["mac"].get<std::string>());
int iters = cr["kdfparams"]["iterations"].get<int>();

std::vector<uint8_t> dk(32);
PKCS5_PBKDF2_HMAC(passphrase.c_str(), (int)[Link](),
[Link](), (int)[Link](), iters,
EVP_sha256(), (int)[Link](), [Link]());

// Verificar MAC
std::vector<uint8_t> mac_key([Link]() + 16, [Link]());
unsigned char mac[32]; unsigned int mac_len = 32;
HMAC(EVP_sha256(), mac_key.data(), (int)mac_key.size(),
[Link](), (int)[Link](), mac, &mac_len);
if (std::vector<uint8_t>(mac, mac+32) != mac_ok)
throw std::runtime_error("Passphrase incorrecta o keystore corrupto");

// Descifrar
std::vector<uint8_t> pt([Link]());
int l1 = 0, l2 = 0;
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, [Link](), [Link]());
EVP_DecryptUpdate(ctx, [Link](), &l1, [Link](), (int)[Link]());
EVP_DecryptFinal_ex(ctx, [Link]() + l1, &l2);
EVP_CIPHER_CTX_free(ctx);
[Link](l1 + l2);
return bytes_to_hex(pt);
}

// ==================== HMAC-SHA256 (L2) ====================

std::string hmac_sha256_b64(const std::string& key, const std::string& msg) {


unsigned char d[32]; unsigned int dlen = 32;
HMAC(EVP_sha256(),
[Link](), (int)[Link](),
reinterpret_cast<const unsigned char*>([Link]()), (int)[Link](),
d, &dlen);
return base64_encode(std::vector<uint8_t>(d, d+32));
}

// ==================== API Credentials ====================

struct ApiCreds {
std::string api_key;
std::string secret;
std::string passphrase;
std::string address;
};

// Headers L2 para cada request


cpr::Header l2_headers(const ApiCreds& c,
const std::string& method,
const std::string& path,
const std::string& body = "") {
auto now = std::chrono::system_clock::now();
std::string ts = std::to_string(
std::chrono::duration_cast<std::chrono::seconds>(
now.time_since_epoch()).count());
std::string sig = hmac_sha256_b64([Link], ts + method + path + body);
return cpr::Header{
{"POLY-API-KEY", c.api_key},
{"POLY-PASSPHRASE", [Link]},
{"POLY-TIMESTAMP", ts},
{"POLY-SIGNATURE", sig},
{"Content-Type", "application/json"}
};
}

// Carga creds desde archivo (generadas una sola vez con py-clob-client)
ApiCreds cargar_creds(const std::string& address) {
std::ifstream f(CREDS_FILE);
if (![Link]()) {
// Instrucciones para generar creds con Python (una sola vez):
// pip install py-clob-client
// python3 -c "
// from py_clob_client.client import ClobClient
// import os, json
// c = ClobClient('[Link] chain_id=137,
// key=[Link]['PRIVATE_KEY'])
// creds = c.create_or_derive_api_creds()
// print([Link]({'apiKey': creds.api_key,
// 'secret': creds.api_secret,
// 'passphrase': creds.api_passphrase}))
// " > api_creds.json
throw std::runtime_error(
"No se encontró api_creds.json.\n"
"Genera las credenciales L2 con py-clob-client (ver comentario en código)\n"
"y guárdalas como: {\"apiKey\":\"...\",\"secret\":\"...\",\"passphrase\":\"...\"}");
}
json j; f >> j;
log_msg(LogLevel::INFO, "API credentials L2 cargadas.");
return {
j["apiKey"].get<std::string>(),
j["secret"].get<std::string>(),
j["passphrase"].get<std::string>(),
address
};
}

// ==================== Estructuras ====================

struct Cobertura {
std::vector<double> precios;
std::vector<double> apuestas;
double inversion_total = 0;
double retorno_minimo = 0;
double ganancia_neta = 0;
double roi = 0;
};
struct Mercado {
std::string slug, question, createdAt, endDate;
std::vector<std::string> token_ids; // uno por outcome
double volumeNum = 0;
std::vector<double> precios;
double suma_precios = 0;
bool hay_arbitraje = false;
Cobertura cobertura;
};

// ==================== Cobertura ====================

Cobertura calcular_cobertura(const std::vector<double>& precios, double pago) {


Cobertura c;
[Link] = precios;
std::vector<double> pesos([Link]());
double sp = 0;
for (size_t i = 0; i < [Link](); ++i) { pesos[i] = 1.0/precios[i]; sp += pesos[i]; }
[Link]([Link]());
c.inversion_total = 0;
std::vector<double> ret([Link]());
for (size_t i = 0; i < [Link](); ++i) {
[Link][i] = (pesos[i]/sp)*pago;
c.inversion_total += [Link][i];
ret[i] = [Link][i]/precios[i];
}
c.retorno_minimo = *std::min_element([Link](), [Link]());
c.ganancia_neta = c.retorno_minimo - c.inversion_total;
[Link] = (c.inversion_total > 0) ? (c.ganancia_neta/c.inversion_total)*100.0 : 0;
return c;
}

// ==================== Verificación live de precios (/price) ====================

// GET /price?token_id=X&side=BUY → precio real en el libro en este momento


// No usa /book (bug conocido con datos stale) — usa /price que es el endpoint correcto
double precio_live(const std::string& token_id) {
auto r = cpr::Get(
cpr::Url{CLOB_API + "/price"},
cpr::Parameters{{"token_id", token_id}, {"side", "BUY"}},
cpr::Timeout{5'000}
);
if (r.status_code != 200) {
log_msg(LogLevel::ERR, "GET /price fallido token=" +
token_id.substr(0,10) + " HTTP " + std::to_string(r.status_code));
return -1.0;
}
try {
auto j = json::parse([Link]);
std::string ps = [Link]("price", "");
if (![Link]()) return std::stod(ps);
} catch (...) {}
return -1.0;
}

// Verifica precios en vivo para todos los outcomes ANTES de ejecutar.


// Devuelve los precios reales si todo está OK, vector vacío si alguno falla.
// Condiciones de fallo:
// 1. /price no responde para algún outcome
// 2. precio live > precio_gamma * (1 + PRICE_SLIPPAGE_MAX) → demasiado slippage
// 3. suma de precios live >= ARBITRAJE_UMBRAL → arbitraje ya desapareció
std::vector<double> verificar_precios_live(
const std::vector<std::string>& token_ids,
const std::vector<double>& precios_gamma) {

std::vector<double> precios_live;
precios_live.reserve(token_ids.size());
double suma_live = 0.0;

for (size_t i = 0; i < token_ids.size(); ++i) {


double p = precio_live(token_ids[i]);

if (p < 0) {
log_msg(LogLevel::WARNING,
"Outcome " + std::to_string(i) + ": /price no respondió — abortando");
return {};
}

double limite = precios_gamma[i] * (1.0 + PRICE_SLIPPAGE_MAX);


if (p > limite) {
log_msg(LogLevel::WARNING,
"Outcome " + std::to_string(i) + ": slippage excesivo "
"gamma=" + std::to_string(precios_gamma[i]) +
" live=" + std::to_string(p) +
" limite=" + std::to_string(limite) + " — abortando");
return {};
}

precios_live.push_back(p);
suma_live += p;

log_msg(LogLevel::INFO,
"Outcome " + std::to_string(i) +
": gamma=" + std::to_string(precios_gamma[i]) +
" live=" + std::to_string(p) + " ✓");
}

// Verificar que el arbitraje sigue siendo válido con los precios reales
if (suma_live >= ARBITRAJE_UMBRAL) {
log_msg(LogLevel::WARNING,
"Arbitraje desapareció: suma_live=" + std::to_string(suma_live) +
" >= umbral=" + std::to_string(ARBITRAJE_UMBRAL) + " — abortando");
return {};
}

log_msg(LogLevel::INFO,
"Precios live verificados ✓ | suma_live=" + std::to_string(suma_live) +
" | arbitraje sigue vigente");
return precios_live;
}

// ==================== Ejecutar órdenes (CLOB L2) ====================

// Envía una sola orden BUY FOK. Devuelve order_id si OK, "" si falla.
std::string enviar_orden(const std::string& token_id, double precio,
double monto, const ApiCreds& creds) {
if (precio <= 0) {
log_msg(LogLevel::WARNING, "Precio inválido, abortando orden");
return "";
}
double shares = monto / precio;
shares = std::floor(shares * 1e6) / 1e6; // truncar a 6 decimales (no redondear)
if (shares <= 0) {
log_msg(LogLevel::WARNING, "Shares <= 0, abortando orden");
return "";
}
if (shares > 1e6) {
log_msg(LogLevel::WARNING, "Shares absurdas (" + std::to_string(shares) + "),
abortando orden");
return "";
}
json order = {
{"tokenID", token_id},
{"price", precio},
{"size", shares},
{"side", "BUY"},
{"orderType", "FOK"}
};
std::string body = [Link]();
std::string path = "/order";
auto r = cpr::Post(cpr::Url{CLOB_API + path},
l2_headers(creds, "POST", path, body),
cpr::Body{body}, cpr::Timeout{10'000});
if (r.status_code == 200 || r.status_code == 201) {
try {
auto resp = json::parse([Link]);
return [Link]("orderID", [Link]("id", "OK"));
} catch (...) { return "OK"; }
}
log_msg(LogLevel::ERR, "Orden fallida token=" + token_id.substr(0,10) +
"... HTTP " + std::to_string(r.status_code) + " " + [Link]);
return "";
}

// Ejecución todo-o-nada para N outcomes categóricos


// Flujo: verificar precios live → recalcular cobertura → ejecutar todos
// Si la verificación previa falla → cero órdenes enviadas, sin necesidad de rollback
bool ejecutar_cobertura(const Mercado& m, const ApiCreds& creds) {
size_t n = m.token_ids.size();
if (n < 2 || n != [Link]() || n != [Link]()) {
log_msg(LogLevel::WARNING, "Datos incompletos para: " + [Link]);
return false;
}

// — Fase 1: verificar precios live ANTES de ejecutar cualquier orden —


auto precios_live = verificar_precios_live(m.token_ids, [Link]);
if (precios_live.empty()) return false; // abortado limpiamente, sin rollback

// — Fase 2: recalcular cobertura con precios reales del libro —


Cobertura cob_live = calcular_cobertura(precios_live, PAGO_BASE);
log_msg(LogLevel::INFO,
"Cobertura recalculada con precios live | ROI=" +
std::to_string(cob_live.roi) + "% | Inversión=$" +
std::to_string(cob_live.inversion_total));

// — Fase 3: ejecutar todas las órdenes con precios live —


for (size_t i = 0; i < n; ++i) {
std::string order_id = enviar_orden(
m.token_ids[i], precios_live[i], cob_live.apuestas[i], creds);

if (order_id.empty()) {
// Caso extremadamente raro: precio se movió entre verificación y ejecución
// No hay rollback posible aquí de forma limpia — logear y alertar
log_msg(LogLevel::ERR,
"CRÍTICO: outcome " + std::to_string(i) +
" falló DESPUÉS de verificación. Revisar posición manualmente. "
"Outcomes ejecutados antes: " + std::to_string(i));
return false;
}

double shares_log = std::floor((cob_live.apuestas[i] / precios_live[i]) * 1e6) / 1e6;


log_msg(LogLevel::INFO,
"Outcome " + std::to_string(i) + "/" + std::to_string(n-1) +
" ejecutado | token=" + m.token_ids[i].substr(0,10) +
"... shares=" + std::to_string(shares_log) +
" @ " + std::to_string(precios_live[i]) +
" (USD $" + std::to_string(cob_live.apuestas[i]) + ")");
}

log_msg(LogLevel::INFO,
"✓ Cobertura completa: " + std::to_string(n) + " outcomes | " +
[Link](0, 55) +
" | ROI live: " + std::to_string(cob_live.roi) + "%" +
" | Ganancia esperada: $" + std::to_string(cob_live.ganancia_neta));
return true;
}

// ==================== Histórico / Cursor / Percentil ====================

json cargar_historico() {
if (std::ifstream f(HISTORICO_FILE); [Link]()) {
try { json j; f >> j; if (j.is_array()) return j; } catch (...) {}
}
return json::array();
}

void guardar_historico(const json& d) {


std::ofstream f(HISTORICO_FILE); f << [Link](2);
}

double umbral_volumen(const json& hist) {


std::vector<double> v;
for (auto& m : hist) { double x = [Link]("volumeNum",0.0); if (x>0) v.push_back(x); }
if ((int)[Link]() < MIN_MUESTRAS_HIST) return VOLUMEN_MAX_INICIAL;
std::sort([Link](), [Link]());
return v[static_cast<size_t>(std::floor(PERCENTIL_VOLUMEN/100.0*([Link]()-1)))];
}

std::string ultimo_created_at(const json& hist) {


std::string mx;
for (auto& m : hist) { std::string ts = [Link]("createdAt",""); if (ts>mx) mx=ts; }
if (![Link]()) return mx;
auto now = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(now);
std::ostringstream ss;
ss << std::put_time(std::gmtime(&t), "%Y-%m-%dT%H:%M:%SZ");
return [Link]();
}
// ==================== Filtros ====================

json parse_campo(const json& m, const std::string& k) {


if (![Link](k)) return json::array();
auto& v = m[k];
if (v.is_array()) return v;
if (v.is_string()) try { return json::parse([Link]<std::string>()); } catch (...) {}
return json::array();
}

double dias_hasta_resolucion(const json& m) {


for (auto& campo : {"endDate","resolutionDate","resolvedAt","end_date"}) {
if (![Link](campo) || m[campo].is_null()) continue;
std::string raw = m[campo].get<std::string>();
std::tm tm = {}; std::istringstream ss(raw);
ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S");
if ([Link]()) continue;
return std::difftime(std::mktime(&tm), std::time(nullptr)) / 86400.0;
}
return -1.0;
}

bool procesar_mercado(const json& m, double umbral_vol, Mercado& out) {


double vol = 0;
try { vol = [Link]("volumeNum", 0.0); } catch (...) { return false; }
if (vol > umbral_vol) return false;

double dias = dias_hasta_resolucion(m);


if (dias <= 0 || dias > MAX_DIAS_RESOLUCION) return false;

// Categórico: requiere 2 o más outcomes mutuamente exclusivos


json outcomes = parse_campo(m, "outcomes");
json prices_r = parse_campo(m, "outcomePrices");
if ([Link]() < 2 || prices_r.size() != [Link]()) return false;

std::vector<double> precios;
try {
for (auto& p : prices_r)
precios.push_back(p.is_string()
? std::stod([Link]<std::string>()) : [Link]<double>());
} catch (...) { return false; }

// Todos los outcomes deben tener liquidez mínima


for (double p : precios)
if (p < SPREAD_MIN) return false;

// Arbitraje estructural: suma < 0.98 (margen neto después de fees del 2%)
double suma = 0; for (double p : precios) suma += p;
if (suma >= ARBITRAJE_UMBRAL) return false; // sin arbitraje, no ejecutar

// Token IDs para cada outcome


json tids = parse_campo(m, "clob_token_ids");
if ([Link]() != [Link]()) return false; // sin token_ids no podemos operar

std::vector<std::string> token_ids;
try {
for (auto& t : tids) token_ids.push_back([Link]<std::string>());
} catch (...) { return false; }

[Link] = [Link]("slug", "");


[Link] = [Link]("question", "Sin título");
[Link] = [Link]("createdAt", "");
[Link] = vol;
[Link] = precios;
out.suma_precios = suma;
out.hay_arbitraje = true; // si llegamos aquí, siempre hay arbitraje
out.token_ids = token_ids;
[Link] = calcular_cobertura(precios, PAGO_BASE);
for (auto& c : {"endDate","resolutionDate","resolvedAt","end_date"})
if ([Link](c) && !m[c].is_null()) { [Link] = m[c].get<std::string>(); break; }
return true;
}

// ==================== Modo: crear keystore ====================

void modo_crear_keystore() {
std::cout << "=== Crear keystore encriptado ===\n";
std::cout << "Private key (hex sin 0x): "; std::string pk; std::cin >> pk;
std::cout << "Passphrase de cifrado: "; std::string pass; std::cin >> pass;
std::cout << "Wallet address (EOA): "; std::string addr; std::cin >> addr;

json ks = crear_keystore(pk, pass);


ks["address"] = addr;
std::ofstream f(KEYSTORE_FILE); f << [Link](2);
std::cout << "✓ Keystore guardado en " << KEYSTORE_FILE << "\n";
std::cout << " Ejecuta: history -c (para borrar la clave del historial)\n";
}

// ==================== Main ====================

int main(int argc, char* argv[]) {


if (argc > 1 && std::string(argv[1]) == "--create-keystore") {
modo_crear_keystore();
return 0;
}
g_logfile.open(LOG_FILE, std::ios::app);

// Cargar y descifrar keystore


json keystore;
{ std::ifstream f(KEYSTORE_FILE); if (![Link]())
throw std::runtime_error("No se encontró " + KEYSTORE_FILE +
"\nEjecuta: ./monitor --create-keystore"); f >> keystore; }

std::cout << "Passphrase del keystore: ";


std::string passphrase; std::cin >> passphrase;

std::string private_key = descifrar_keystore(keystore, passphrase);


std::string address = [Link]("address", "");
log_msg(LogLevel::INFO, "Keystore OK. Address: " + address);
std::fill([Link](), [Link](), '\0');

// Cargar credenciales L2
ApiCreds creds = cargar_creds(address);
std::fill(private_key.begin(), private_key.end(), '\0');

// Estado del monitor


json historico = cargar_historico();
std::set<std::string> vistos;
for (auto& m : historico) [Link]([Link]("slug",""));

std::string ultimo_ts = ultimo_created_at(historico);


std::deque<double> cola_roi;
int ciclo=0, total_nuevos=0, total_arb=0, total_ordenes=0;

log_msg(LogLevel::INFO, "Monitor iniciado. createdAfter=" + ultimo_ts);

while (true) {
++ciclo;
double umbral = umbral_volumen(historico);
std::string max_ts = ultimo_ts;

// Consulta API Gamma


auto r = cpr::Get(
cpr::Url{GAMMA_API},
cpr::Parameters{
{"active","true"},{"closed","false"},
{"order","createdAt"},{"ascending","true"},
{"limit", std::to_string(API_LIMIT)},
{"createdAfter", ultimo_ts}
},
cpr::Timeout{10'000}
);
if (r.status_code != 200) {
log_msg(LogLevel::ERR, "API HTTP " + std::to_string(r.status_code));
std::this_thread::sleep_for(std::chrono::seconds(POLLING_SEC));
continue;
}

json mercados;
try { mercados = json::parse([Link]); }
catch (const std::exception& e) {
log_msg(LogLevel::ERR, std::string("JSON: ") + [Link]());
std::this_thread::sleep_for(std::chrono::seconds(POLLING_SEC));
continue;
}

for (auto& m : mercados) {


std::string slug = [Link]("slug","");
std::string cat = [Link]("createdAt","");
if ([Link]() || [Link](slug)) continue;
[Link](slug);
if (cat > max_ts) max_ts = cat; // cursor avanza aunque no pase filtros

Mercado res;
if (!procesar_mercado(m, umbral, res)) continue;

historico.push_back({
{"slug",[Link]},{"question",[Link]},
{"createdAt",[Link]},{"endDate",[Link]},
{"volumeNum",[Link]},{"precios",[Link]},
{"suma_precios",res.suma_precios},{"hay_arbitraje",res.hay_arbitraje},
{"cobertura",{
{"apuestas",[Link]},
{"inversion_total",[Link].inversion_total},
{"retorno_minimo",[Link].retorno_minimo},
{"ganancia_neta",[Link].ganancia_neta},
{"roi_%",[Link]}
}}
});

++total_nuevos;
if ((int)cola_roi.size() >= COLA_ROI_MAX) cola_roi.pop_front();
cola_roi.push_back([Link]);

// Solo llegamos aquí si hay arbitraje (procesar_mercado lo garantiza)


++total_arb;
std::ostringstream oss;
oss << "[ARBITRAJE x" << [Link]() << "] "
<< [Link](0,55)
<< "\n Outcomes: " << [Link]()
<< " | Suma precios: " << res.suma_precios
<< " | ROI: " << [Link] << "%"
<< " | Inversion: $" << [Link].inversion_total
<< " | Ganancia: $" << [Link].ganancia_neta;
oss << "\n Apuestas: [";
for (size_t i = 0; i < [Link](); ++i)
oss << "$" << [Link][i]
<< (i+1 < [Link]() ? ", " : "]");
oss << "\n URL: [Link] << slug;
log_msg(LogLevel::WARNING, [Link]());

// Ejecutar cobertura automáticamente


if (ejecutar_cobertura(res, creds)) ++total_ordenes;
}

guardar_historico(historico);
ultimo_ts = max_ts;

if (ciclo % RESUMEN_CADA == 0 && !cola_roi.empty()) {


double roi_med = std::accumulate(cola_roi.begin(),cola_roi.end(),0.0)
/ (double)cola_roi.size();
std::ostringstream oss;
oss << "\n" << std::string(50,'-') << "\n"
<< " Ciclo #" << ciclo << " | Umbral: $" << umbral << "\n"
<< " Cursor: " << ultimo_ts << "\n"
<< " Mercados: " << total_nuevos
<< " | Arbitrajes: "<< total_arb
<< " | Ordenes: " << total_ordenes << "\n"
<< " ROI medio: " << roi_med << "%\n"
<< std::string(50,'-');
log_msg(LogLevel::INFO, [Link]());
}

std::this_thread::sleep_for(std::chrono::seconds(POLLING_SEC));
}
}

You might also like