/* Live BTCUSDT indicator with the Wickra C ABI. * * The C counterpart of `examples/rust/src/bin/live_binance.rs`, * `examples/python/live_binance.py` and `examples/node/live_binance.js`. Those * stream Binance over a WebSocket; the C ABI ships only the indicators and no * socket layer, so this example polls the Binance REST klines endpoint via the * system `curl` once per interval and feeds each newly *closed* candle into a * streaming RSI(14). Same "live feed -> incremental indicator" shape, no extra * dependency. * * This example talks to the network and runs until interrupted (Ctrl+C), so it * is built but NOT run as a ctest. * * Build (after `cargo build -p wickra-c --release`): * cc examples/c/live_binance.c -I bindings/c/include -L target/release -lwickra -lm -o live_binance * ./live_binance [SYMBOL] */ #include "wickra.h" #include #include #include #include #include #ifdef _WIN32 #include #define SLEEP_MS(ms) Sleep(ms) #define POPEN _popen #define PCLOSE _pclose #else #include #define POPEN popen #define PCLOSE pclose static void SLEEP_MS(long ms) { struct timespec ts = {ms / 1000, (ms % 1000) * 1000000L}; nanosleep(&ts, NULL); } #endif /* Run `curl ` and return stdout as a malloc'd, NUL-terminated buffer. */ static char *curl_get(const char *url) { char cmd[512]; snprintf(cmd, sizeof(cmd), "curl --silent --show-error --fail --max-time 15 \"%s\"", url); FILE *p = POPEN(cmd, "r"); if (p == NULL) { return NULL; } size_t cap = 1 << 15, len = 0; char *buf = (char *)malloc(cap); if (buf == NULL) { PCLOSE(p); return NULL; } size_t got; char tmp[4096]; while ((got = fread(tmp, 1, sizeof(tmp), p)) > 0) { if (len + got + 1 > cap) { cap *= 2; char *grown = (char *)realloc(buf, cap); if (grown == NULL) { free(buf); PCLOSE(p); return NULL; } buf = grown; } memcpy(buf + len, tmp, got); len += got; } int rc = PCLOSE(p); buf[len] = '\0'; if (rc != 0 || len == 0) { free(buf); return NULL; } return buf; } /* Extract the first kline's open time and close price from a klines response of * the form [[openTime,"open","high","low","close",...],...]. Returns 1 on * success. The first row (limit=2) is the most recent fully closed candle. */ static int first_kline(const char *body, int64_t *open_time, double *close) { const char *s = strchr(body, '['); if (s == NULL) { return 0; } s = strchr(s + 1, '['); /* into the first row */ if (s == NULL) { return 0; } s++; char tok[64]; int field = 0; while (*s && *s != ']') { size_t tl = 0; while (*s && *s != ',' && *s != ']') { if (*s != '"' && tl + 1 < sizeof(tok)) { tok[tl++] = *s; } s++; } tok[tl] = '\0'; if (field == 0) { *open_time = strtoll(tok, NULL, 10); } else if (field == 4) { *close = strtod(tok, NULL); return 1; } field++; if (*s == ',') { s++; } } return 0; } int main(int argc, char **argv) { const char *symbol = (argc > 1) ? argv[1] : "BTCUSDT"; char url[256]; snprintf(url, sizeof(url), "https://api.binance.com/api/v3/klines?symbol=%s&interval=1m&limit=2", symbol); struct Rsi *rsi = wickra_rsi_new(14); if (rsi == NULL) { fprintf(stderr, "failed to create RSI\n"); return 1; } printf("Listening for %s 1m closes (REST poll, Ctrl+C to stop)...\n", symbol); int64_t last_open = 0; for (;;) { char *body = curl_get(url); if (body != NULL) { int64_t open_time = 0; double close = 0.0; if (first_kline(body, &open_time, &close) && open_time != last_open) { last_open = open_time; double v = wickra_rsi_update(rsi, close); if (isfinite(v)) { printf("%s close=%.4f rsi=%.2f\n", symbol, close, v); } else { printf("%s close=%.4f rsi=...warmup\n", symbol, close); } fflush(stdout); } free(body); } SLEEP_MS(2000); } /* Unreachable in normal use (interrupted by Ctrl+C). */ }