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:
|
async def start(self) -> None:
|
||||||
"""Pre-initialize and warm up the aiohttp session so the first trade is not slowed."""
|
"""Pre-initialize and warm up the aiohttp session so the first trade is not slowed."""
|
||||||
self._session = self._create_session()
|
self._session = self._create_session()
|
||||||
async def _warm_up():
|
await self._api.warmup_session(self._session)
|
||||||
try:
|
|
||||||
async with self._session.get("https://api.kucoin.com/api/v1/time") as resp:
|
|
||||||
await resp.text()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
await _warm_up()
|
|
||||||
|
|
||||||
self._keepalive_task = asyncio.create_task(self._keepalive_loop())
|
self._keepalive_task = asyncio.create_task(self._keepalive_loop())
|
||||||
|
|
||||||
_KEEPALIVE_INTERVAL = 15.0
|
_KEEPALIVE_INTERVAL = 30.0
|
||||||
|
|
||||||
def _create_session(self) -> aiohttp.ClientSession:
|
def _create_session(self) -> aiohttp.ClientSession:
|
||||||
connector = TCPConnector(
|
connector = TCPConnector(
|
||||||
|
|
@ -269,13 +263,12 @@ class Executor:
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _keepalive_loop(self) -> None:
|
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:
|
while True:
|
||||||
try:
|
try:
|
||||||
await asyncio.sleep(self._KEEPALIVE_INTERVAL)
|
await asyncio.sleep(self._KEEPALIVE_INTERVAL)
|
||||||
if self._live_mode:
|
if self._live_mode:
|
||||||
async with self._session.get("https://api.kucoin.com/api/v1/time") as resp:
|
await self._api.warmup_session(self._session)
|
||||||
await resp.text()
|
|
||||||
else:
|
else:
|
||||||
await self._api.order_test(
|
await self._api.order_test(
|
||||||
session=self._session,
|
session=self._session,
|
||||||
|
|
@ -703,6 +696,10 @@ class Executor:
|
||||||
await self._ws_client.await_balance(
|
await self._ws_client.await_balance(
|
||||||
output_ccy, fills[-1].filled_volume, 2000
|
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)
|
in_flight.last_trade_ts_ms = int(time.time() * 1000)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -432,3 +432,26 @@ class KuCoinAPI:
|
||||||
except (aiohttp.ClientError, OSError) as e:
|
except (aiohttp.ClientError, OSError) as e:
|
||||||
self._log.error("private_token_http_error", error=str(e))
|
self._log.error("private_token_http_error", error=str(e))
|
||||||
return None
|
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:
|
if self._balance_futures.get(key) is future:
|
||||||
del self._balance_futures[key]
|
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:
|
async def _connection_worker(self) -> None:
|
||||||
"""Main connection loop with exponential backoff reconnection."""
|
"""Main connection loop with exponential backoff reconnection."""
|
||||||
while self._running:
|
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] = "";
|
char books_json_str[2048] = "";
|
||||||
if (!sig->live && sig->book_count > 0) {
|
if (sig->book_count > 0) {
|
||||||
char *bp = books_json_str;
|
char *bp = books_json_str;
|
||||||
size_t rem = sizeof(books_json_str);
|
size_t rem = sizeof(books_json_str);
|
||||||
for (uint8_t b = 0; b < sig->book_count; b++) {
|
for (uint8_t b = 0; b < sig->book_count; b++) {
|
||||||
const signal_book_t *sb = &sig->books[b];
|
const signal_book_t *sb = &sig->books[b];
|
||||||
char bid_arr[256] = {0}, ask_arr[256] = {0};
|
char bid_arr[256] = {0}, ask_arr[256] = {0};
|
||||||
for (uint8_t lev = 0; lev < sb->bid_count; lev++) {
|
// Only top-of-book level included for display / drift analysis
|
||||||
char tmp[64];
|
char tmp[64];
|
||||||
snprintf(tmp, sizeof(tmp), "%s{\"price\":\"%.6g\",\"size\":\"%.8g\"}",
|
if (sb->bid_count > 0) {
|
||||||
lev ? "," : "", sb->bids[lev].price, sb->bids[lev].size);
|
snprintf(tmp, sizeof(tmp), "{\"price\":\"%.6g\",\"size\":\"%.8g\"}",
|
||||||
|
sb->bids[0].price, sb->bids[0].size);
|
||||||
strncat(bid_arr, tmp, sizeof(bid_arr) - 1);
|
strncat(bid_arr, tmp, sizeof(bid_arr) - 1);
|
||||||
}
|
}
|
||||||
for (uint8_t lev = 0; lev < sb->ask_count; lev++) {
|
if (sb->ask_count > 0) {
|
||||||
char tmp[64];
|
snprintf(tmp, sizeof(tmp), "{\"price\":\"%.6g\",\"size\":\"%.8g\"}",
|
||||||
snprintf(tmp, sizeof(tmp), "%s{\"price\":\"%.6g\",\"size\":\"%.8g\"}",
|
sb->asks[0].price, sb->asks[0].size);
|
||||||
lev ? "," : "", sb->asks[lev].price, sb->asks[lev].size);
|
|
||||||
strncat(ask_arr, tmp, sizeof(ask_arr) - 1);
|
strncat(ask_arr, tmp, sizeof(ask_arr) - 1);
|
||||||
}
|
}
|
||||||
int n = snprintf(bp, rem,
|
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->ts_ms, (long long)sig->book_ts_ms,
|
||||||
(long long)sig->t_sock_arrive_ms,
|
(long long)sig->t_sock_arrive_ms,
|
||||||
(long long)sig->t_arrive_ms, (long long)sig->t_eval_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 : "",
|
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 to_send = strlen(json_buf);
|
||||||
size_t sent = 0;
|
size_t sent = 0;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue