de1112ea91
The examples stream a live Binance feed into the indicators and print signals;
they place no orders, so 'live_trading' overstated them and was inconsistent
with the C/Go/R examples already named live_binance. Rename the Python/Node/WASM
files to live_binance.* and update every reference, run command, header, and the
project-tree listings. Accurate use-case wording ('suitable for live trading
bots') and the risk disclaimers are left unchanged.
155 lines
4.5 KiB
C
155 lines
4.5 KiB
C
/* 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 <math.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#define SLEEP_MS(ms) Sleep(ms)
|
|
#define POPEN _popen
|
|
#define PCLOSE _pclose
|
|
#else
|
|
#include <time.h>
|
|
#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 <url>` 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). */
|
|
}
|