Merge pull request 'Fix overnight runner stability and token cooldown handling' (#139) from agentson/fix/137-run-overnight-python-tmux into main
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
Reviewed-on: #139
This commit was merged in pull request #139.
This commit is contained in:
@@ -315,6 +315,11 @@ class SmartVolatilityScanner:
|
|||||||
logger.info("Overseas scanner: no symbol universe for %s", market.name)
|
logger.info("Overseas scanner: no symbol universe for %s", market.name)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Overseas scanner: scanning %d fallback symbols for %s",
|
||||||
|
len(symbols),
|
||||||
|
market.name,
|
||||||
|
)
|
||||||
candidates: list[ScanCandidate] = []
|
candidates: list[ScanCandidate] = []
|
||||||
for stock_code in symbols:
|
for stock_code in symbols:
|
||||||
try:
|
try:
|
||||||
@@ -350,6 +355,11 @@ class SmartVolatilityScanner:
|
|||||||
logger.warning("Failed to analyze overseas %s: %s", stock_code, exc)
|
logger.warning("Failed to analyze overseas %s: %s", stock_code, exc)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("Unexpected error analyzing overseas %s: %s", stock_code, exc)
|
logger.error("Unexpected error analyzing overseas %s: %s", stock_code, exc)
|
||||||
|
logger.info(
|
||||||
|
"Overseas symbol fallback scan found %d candidates for %s",
|
||||||
|
len(candidates),
|
||||||
|
market.name,
|
||||||
|
)
|
||||||
return candidates
|
return candidates
|
||||||
|
|
||||||
def get_stock_codes(self, candidates: list[ScanCandidate]) -> list[str]:
|
def get_stock_codes(self, candidates: list[ScanCandidate]) -> list[str]:
|
||||||
|
|||||||
@@ -104,12 +104,14 @@ class KISBroker:
|
|||||||
time_since_last_attempt = now - self._last_refresh_attempt
|
time_since_last_attempt = now - self._last_refresh_attempt
|
||||||
if time_since_last_attempt < self._refresh_cooldown:
|
if time_since_last_attempt < self._refresh_cooldown:
|
||||||
remaining = self._refresh_cooldown - time_since_last_attempt
|
remaining = self._refresh_cooldown - time_since_last_attempt
|
||||||
error_msg = (
|
# Do not fail fast here. If token is unavailable, upstream calls
|
||||||
f"Token refresh on cooldown. "
|
# will all fail for up to a minute and scanning returns no trades.
|
||||||
f"Retry in {remaining:.1f}s (KIS allows 1/minute)"
|
logger.warning(
|
||||||
|
"Token refresh on cooldown. Waiting %.1fs before retry (KIS allows 1/minute)",
|
||||||
|
remaining,
|
||||||
)
|
)
|
||||||
logger.warning(error_msg)
|
await asyncio.sleep(remaining)
|
||||||
raise ConnectionError(error_msg)
|
now = asyncio.get_event_loop().time()
|
||||||
|
|
||||||
logger.info("Refreshing KIS access token")
|
logger.info("Refreshing KIS access token")
|
||||||
self._last_refresh_attempt = now
|
self._last_refresh_attempt = now
|
||||||
|
|||||||
@@ -82,14 +82,19 @@ class OverseasBroker:
|
|||||||
session = self._broker._get_session()
|
session = self._broker._get_session()
|
||||||
|
|
||||||
if ranking_type == "volume":
|
if ranking_type == "volume":
|
||||||
tr_id = self._broker._settings.OVERSEAS_RANKING_VOLUME_TR_ID
|
configured_tr_id = self._broker._settings.OVERSEAS_RANKING_VOLUME_TR_ID
|
||||||
path = self._broker._settings.OVERSEAS_RANKING_VOLUME_PATH
|
configured_path = self._broker._settings.OVERSEAS_RANKING_VOLUME_PATH
|
||||||
|
default_tr_id = "HHDFS76200200"
|
||||||
|
default_path = "/uapi/overseas-price/v1/quotations/inquire-volume-rank"
|
||||||
else:
|
else:
|
||||||
tr_id = self._broker._settings.OVERSEAS_RANKING_FLUCT_TR_ID
|
configured_tr_id = self._broker._settings.OVERSEAS_RANKING_FLUCT_TR_ID
|
||||||
path = self._broker._settings.OVERSEAS_RANKING_FLUCT_PATH
|
configured_path = self._broker._settings.OVERSEAS_RANKING_FLUCT_PATH
|
||||||
|
default_tr_id = "HHDFS76200100"
|
||||||
|
default_path = "/uapi/overseas-price/v1/quotations/inquire-updown-rank"
|
||||||
|
|
||||||
headers = await self._broker._auth_headers(tr_id)
|
endpoint_specs: list[tuple[str, str]] = [(configured_tr_id, configured_path)]
|
||||||
url = f"{self._broker._base_url}{path}"
|
if (configured_tr_id, configured_path) != (default_tr_id, default_path):
|
||||||
|
endpoint_specs.append((default_tr_id, default_path))
|
||||||
|
|
||||||
# Try common param variants used by KIS overseas quotation APIs.
|
# Try common param variants used by KIS overseas quotation APIs.
|
||||||
param_variants = [
|
param_variants = [
|
||||||
@@ -100,24 +105,38 @@ class OverseasBroker:
|
|||||||
]
|
]
|
||||||
|
|
||||||
last_error: str | None = None
|
last_error: str | None = None
|
||||||
for params in param_variants:
|
saw_http_404 = False
|
||||||
try:
|
for tr_id, path in endpoint_specs:
|
||||||
async with session.get(url, headers=headers, params=params) as resp:
|
headers = await self._broker._auth_headers(tr_id)
|
||||||
text = await resp.text()
|
url = f"{self._broker._base_url}{path}"
|
||||||
if resp.status != 200:
|
for params in param_variants:
|
||||||
last_error = f"HTTP {resp.status}: {text}"
|
try:
|
||||||
continue
|
async with session.get(url, headers=headers, params=params) as resp:
|
||||||
|
text = await resp.text()
|
||||||
|
if resp.status != 200:
|
||||||
|
last_error = f"HTTP {resp.status}: {text}"
|
||||||
|
if resp.status == 404:
|
||||||
|
saw_http_404 = True
|
||||||
|
continue
|
||||||
|
|
||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
rows = self._extract_ranking_rows(data)
|
rows = self._extract_ranking_rows(data)
|
||||||
if rows:
|
if rows:
|
||||||
return rows[:limit]
|
return rows[:limit]
|
||||||
|
|
||||||
# keep trying another param variant if response has no usable rows
|
# keep trying another param variant if response has no usable rows
|
||||||
last_error = f"empty output (keys={list(data.keys())})"
|
last_error = f"empty output (keys={list(data.keys())})"
|
||||||
except (TimeoutError, aiohttp.ClientError) as exc:
|
except (TimeoutError, aiohttp.ClientError) as exc:
|
||||||
last_error = str(exc)
|
last_error = str(exc)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if saw_http_404:
|
||||||
|
logger.warning(
|
||||||
|
"Overseas ranking endpoint unavailable (404) for %s/%s; using symbol fallback scan",
|
||||||
|
exchange_code,
|
||||||
|
ranking_type,
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
raise ConnectionError(
|
raise ConnectionError(
|
||||||
f"fetch_overseas_rankings failed for {exchange_code}/{ranking_type}: {last_error}"
|
f"fetch_overseas_rankings failed for {exchange_code}/{ranking_type}: {last_error}"
|
||||||
|
|||||||
Reference in New Issue
Block a user