From 930365f0726b4da94f4483547def374e8e9b0d09 Mon Sep 17 00:00:00 2001 From: nicolas Date: Sat, 30 May 2026 11:22:16 -0300 Subject: [PATCH] feat: add allow_same_quote config, fix overlapping pair isolation to check all 3 legs, document config example --- config.yaml.example | 31 +++++++++++++++++++++++++++++++ src/config.c | 3 +++ src/config.h | 1 + src/executor.c | 20 +++++++++++++------- src/executor.h | 2 +- 5 files changed, 49 insertions(+), 8 deletions(-) diff --git a/config.yaml.example b/config.yaml.example index 10bfe31..4ce522f 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -1,23 +1,54 @@ +# Live trading mode. false = paper trades (test endpoint, no real orders). live_mode: false fused_engine: + # Log verbosity: INFO, DEBUG, or WARN log_level: INFO + + # Minimum predicted profit in basis points (0.01%) to fire a signal signal_threshold_bps: 2 + + # Currencies to exclude from triangle enumeration entirely excluded_currencies: [EUR, BRL] + + # Currencies the bot holds as capital. Triangles must start and end in one of these. hold_currencies: [USDT, USDC, USD1] + + # KuCoin WebSocket base URL (override for alternate endpoint) ws_url: wss://ws-api-spot.kucoin.com + + # KuCoin token endpoint (public bullet for WS credentials) token_url: https://api.kucoin.com/api/v1/bullet-public + + # Initial WebSocket reconnect delay in seconds (exponential backoff) reconnect_base_delay: 1.0 + + # Maximum WebSocket reconnect delay in seconds reconnect_max_delay: 60.0 + + # WebSocket ping interval in seconds heartbeat_interval: 18.0 + + # If true, apply 20% discount to fee rates (KCS holding benefit) kcs_discount_active: false + + # Number of parallel executor threads (each owns one signal slot, max 16) concurrent_slots: 1 + + # Wait for WS balance settlement between live legs (requires private WS channel) balance_wait_enabled: false + + # Allow parallel triangles that share the same primary quote currency + # Useful when exchange balance can support multiple concurrent trades + allow_same_quote: false + + # Maximum quote amount per triangle signal, per currency initial_capital: USDT: 5 USDC: 5 USD1: 5 +# KuCoin API credentials for authenticated endpoints (top-level keys) kucoin_api_key: "" kucoin_api_secret: "" kucoin_api_passphrase: "" diff --git a/src/config.c b/src/config.c index 45dbb37..227ef3c 100644 --- a/src/config.c +++ b/src/config.c @@ -80,6 +80,8 @@ static void handle_value(parse_state_t *st, const char *val) { st->cfg->concurrent_slots = atoi(val); } else if (strcmp(key, "balance_wait_enabled") == 0) { st->cfg->balance_wait_enabled = (strcmp(val, "true") == 0 || strcmp(val, "yes") == 0); + } else if (strcmp(key, "allow_same_quote") == 0) { + st->cfg->allow_same_quote = (strcmp(val, "true") == 0 || strcmp(val, "yes") == 0); } } else if (strcmp(st->section, "executor") == 0) { return; @@ -124,6 +126,7 @@ int config_load(const char *path, config_t *cfg) { cfg->concurrent_slots = 1; cfg->live_mode = false; cfg->balance_wait_enabled = false; + cfg->allow_same_quote = false; FILE *f = fopen(path, "r"); if (!f) { diff --git a/src/config.h b/src/config.h index f1fb4c1..6fdf266 100644 --- a/src/config.h +++ b/src/config.h @@ -37,6 +37,7 @@ typedef struct { int concurrent_slots; /* number of executor threads (1 = single-threaded) */ bool live_mode; /* live trading vs paper/simulation */ bool balance_wait_enabled; /* wait for WS balance settlement between legs */ + bool allow_same_quote; /* allow same primary quote in parallel triangles */ /* Capital allocation limits — each entry maps a currency ticker to a max * quote amount the fused engine may deploy for any one triangle signal. */ diff --git a/src/executor.c b/src/executor.c index 726e033..248377c 100644 --- a/src/executor.c +++ b/src/executor.c @@ -146,16 +146,18 @@ void executor_execute_triangle(executor_thread_t *et, log_write("[EXEC] Dropping signal for overlapping triangle: %s\n", sig->triangle_key); return; } - if (strcmp(sh->primary_quotes[i], sig->primary_quote) == 0) { + if (!et->cfg->allow_same_quote && strcmp(sh->primary_quotes[i], sig->primary_quote) == 0) { pthread_mutex_unlock(&sh->lock); log_write("[EXEC] Dropping signal for same primary quote: %s\n", sig->triangle_key); return; } for (int p = 0; p < 3; p++) { - if (sh->pairs[i] == pair_hashes[p]) { - pthread_mutex_unlock(&sh->lock); - log_write("[EXEC] Dropping signal for overlapping pair on %s\n", sig->triangle_key); - return; + for (int k = 0; k < 3; k++) { + if (sh->pairs[i][k] == pair_hashes[p]) { + pthread_mutex_unlock(&sh->lock); + log_write("[EXEC] Dropping signal for overlapping pair on %s\n", sig->triangle_key); + return; + } } } } @@ -167,7 +169,9 @@ void executor_execute_triangle(executor_thread_t *et, if (slot >= 0) { strncpy(sh->triangles[slot], sig->triangle_key, sizeof(sh->triangles[slot]) - 1); strncpy(sh->primary_quotes[slot], sig->primary_quote, sizeof(sh->primary_quotes[slot]) - 1); - sh->pairs[slot] = pair_hashes[0]; + sh->pairs[slot][0] = pair_hashes[0]; + sh->pairs[slot][1] = pair_hashes[1]; + sh->pairs[slot][2] = pair_hashes[2]; sh->count++; } pthread_mutex_unlock(&sh->lock); @@ -493,7 +497,9 @@ void executor_execute_triangle(executor_thread_t *et, if (strcmp(sh->triangles[i], sig->triangle_key) == 0) { sh->triangles[i][0] = '\0'; sh->primary_quotes[i][0] = '\0'; - sh->pairs[i] = 0; + sh->pairs[i][0] = 0; + sh->pairs[i][1] = 0; + sh->pairs[i][2] = 0; if (sh->count > 0) sh->count--; break; } diff --git a/src/executor.h b/src/executor.h index 1ef3879..982273a 100644 --- a/src/executor.h +++ b/src/executor.h @@ -15,7 +15,7 @@ typedef struct { pthread_mutex_t lock; char triangles[MAX_IN_FLIGHT][128]; - uint64_t pairs[MAX_IN_FLIGHT]; + uint64_t pairs[MAX_IN_FLIGHT][3]; char primary_quotes[MAX_IN_FLIGHT][16]; int count; char last_triangles[MAX_TRACKED_TRIANGLES][128];