fix: authenticated session warmup, balance-aware cascade, books always in signal
executor/executor.py: - Replace unauthenticated /api/v1/time warmup with authenticated /api/v1/accounts - Keepalive interval 15s -> 30s, uses authenticated warmup_session - After sell leg, override filled_volume with latest balance from WS (net of fee) executor/kucoin_api.py: - Add warmup_session() method for GET /api/v1/accounts (authenticated) - Pre-heats TCP/TLS connection pool to reduce first-order latency executor/ws_client.py: - Add latest_balance() method to expose WS balance cache src/events.c: - Always include book tops in signal (remove !sig->live gate) - Only serialize top bid/ask level (not all 5)
This commit is contained in:
parent
0d3acc62cb
commit
affe18cbac
|
|
@ -242,17 +242,11 @@ class Executor:
|
|||
async def start(self) -> None:
|
||||
"""Pre-initialize and warm up the aiohttp session so the first trade is not slowed."""
|
||||
self._session = self._create_session()
|
||||
async def _warm_up():
|
||||
try:
|
||||
async with self._session.get("https://api.kucoin.com/api/v1/time") as resp:
|
||||
await resp.text()
|
||||
except Exception:
|
||||
pass
|
||||
await _warm_up()
|
||||
await self._api.warmup_session(self._session)
|
||||
|
||||
self._keepalive_task = asyncio.create_task(self._keepalive_loop())
|
||||
|
||||
_KEEPALIVE_INTERVAL = 15.0
|
||||
_KEEPALIVE_INTERVAL = 30.0
|
||||
|
||||
def _create_session(self) -> aiohttp.ClientSession:
|
||||
connector = TCPConnector(
|
||||
|
|
@ -269,13 +263,12 @@ class Executor:
|
|||
)
|
||||
|
||||
async def _keepalive_loop(self) -> None:
|
||||
"""Ping KuCoin periodically to keep the authenticated POST path warm."""
|
||||
"""Ping KuCoin periodically to keep the authenticated connection pool warm."""
|
||||
while True:
|
||||
try:
|
||||
await asyncio.sleep(self._KEEPALIVE_INTERVAL)
|
||||
if self._live_mode:
|
||||
async with self._session.get("https://api.kucoin.com/api/v1/time") as resp:
|
||||
await resp.text()
|
||||
await self._api.warmup_session(self._session)
|
||||
else:
|
||||
await self._api.order_test(
|
||||
session=self._session,
|
||||
|
|
@ -703,6 +696,10 @@ class Executor:
|
|||
await self._ws_client.await_balance(
|
||||
output_ccy, fills[-1].filled_volume, 2000
|
||||
)
|
||||
if side == "sell":
|
||||
bal = self._ws_client.latest_balance(output_ccy)
|
||||
if bal > 0:
|
||||
fills[-1].filled_volume = bal
|
||||
in_flight.last_trade_ts_ms = int(time.time() * 1000)
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -432,3 +432,26 @@ class KuCoinAPI:
|
|||
except (aiohttp.ClientError, OSError) as e:
|
||||
self._log.error("private_token_http_error", error=str(e))
|
||||
return None
|
||||
|
||||
async def warmup_session(self, session: aiohttp.ClientSession) -> None:
|
||||
"""Warm up the authenticated HTTP connection pool with a minimal GET."""
|
||||
timestamp = str(int(time.time() * 1000))
|
||||
path = "/api/v1/accounts"
|
||||
sign = _signRequest(timestamp, "GET", path, self._api_secret, "")
|
||||
headers = {
|
||||
"KC-API-TIMESTAMP": timestamp,
|
||||
"KC-API-SIGN": sign,
|
||||
"KC-API-KEY": self._api_key,
|
||||
"KC-API-PASSPHRASE": self._encrypted_passphrase,
|
||||
"KC-API-SIGN-TYPE": "2",
|
||||
"KC-API-KEY-VERSION": "3",
|
||||
}
|
||||
try:
|
||||
async with session.get(
|
||||
f"https://api.kucoin.com{path}",
|
||||
headers=headers,
|
||||
) as resp:
|
||||
await resp.read()
|
||||
self._log.info("session_warmed", status=resp.status)
|
||||
except Exception as e:
|
||||
self._log.warning("session_warmup_failed", error=str(e))
|
||||
|
|
|
|||
|
|
@ -239,6 +239,10 @@ class KuCoinWSClient:
|
|||
if self._balance_futures.get(key) is future:
|
||||
del self._balance_futures[key]
|
||||
|
||||
def latest_balance(self, currency: str) -> Decimal:
|
||||
"""Return the latest known available balance for *currency*, or 0."""
|
||||
return self._latest_balance.get(currency.upper(), _D0)
|
||||
|
||||
async def _connection_worker(self) -> None:
|
||||
"""Main connection loop with exponential backoff reconnection."""
|
||||
while self._running:
|
||||
|
|
|
|||
24
src/events.c
24
src/events.c
|
|
@ -257,24 +257,24 @@ static void send_signal_to_executor(event_loops_t *loops, signal_entry_t *sig) {
|
|||
}
|
||||
}
|
||||
|
||||
// Full book snapshot included when !live (paper trading mode)
|
||||
// Book tops included for display and drift analysis
|
||||
char books_json_str[2048] = "";
|
||||
if (!sig->live && sig->book_count > 0) {
|
||||
if (sig->book_count > 0) {
|
||||
char *bp = books_json_str;
|
||||
size_t rem = sizeof(books_json_str);
|
||||
for (uint8_t b = 0; b < sig->book_count; b++) {
|
||||
const signal_book_t *sb = &sig->books[b];
|
||||
char bid_arr[256] = {0}, ask_arr[256] = {0};
|
||||
for (uint8_t lev = 0; lev < sb->bid_count; lev++) {
|
||||
char tmp[64];
|
||||
snprintf(tmp, sizeof(tmp), "%s{\"price\":\"%.6g\",\"size\":\"%.8g\"}",
|
||||
lev ? "," : "", sb->bids[lev].price, sb->bids[lev].size);
|
||||
// Only top-of-book level included for display / drift analysis
|
||||
char tmp[64];
|
||||
if (sb->bid_count > 0) {
|
||||
snprintf(tmp, sizeof(tmp), "{\"price\":\"%.6g\",\"size\":\"%.8g\"}",
|
||||
sb->bids[0].price, sb->bids[0].size);
|
||||
strncat(bid_arr, tmp, sizeof(bid_arr) - 1);
|
||||
}
|
||||
for (uint8_t lev = 0; lev < sb->ask_count; lev++) {
|
||||
char tmp[64];
|
||||
snprintf(tmp, sizeof(tmp), "%s{\"price\":\"%.6g\",\"size\":\"%.8g\"}",
|
||||
lev ? "," : "", sb->asks[lev].price, sb->asks[lev].size);
|
||||
if (sb->ask_count > 0) {
|
||||
snprintf(tmp, sizeof(tmp), "{\"price\":\"%.6g\",\"size\":\"%.8g\"}",
|
||||
sb->asks[0].price, sb->asks[0].size);
|
||||
strncat(ask_arr, tmp, sizeof(ask_arr) - 1);
|
||||
}
|
||||
int n = snprintf(bp, rem,
|
||||
|
|
@ -298,9 +298,9 @@ static void send_signal_to_executor(event_loops_t *loops, signal_entry_t *sig) {
|
|||
(long long)sig->ts_ms, (long long)sig->book_ts_ms,
|
||||
(long long)sig->t_sock_arrive_ms,
|
||||
(long long)sig->t_arrive_ms, (long long)sig->t_eval_ms,
|
||||
(sig->live || sig->book_count == 0) ? "" : ",\"books\":[",
|
||||
sig->book_count == 0 ? "" : ",\"books\":[",
|
||||
books_json_str[0] ? books_json_str : "",
|
||||
(sig->live || sig->book_count == 0) ? "" : "]");
|
||||
sig->book_count == 0 ? "" : "]");
|
||||
|
||||
size_t to_send = strlen(json_buf);
|
||||
size_t sent = 0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue