반복적인 IPC 오류 해결, 봇 오류 해결, 인증 오류 해결, 서버 자원 할당 오류 해결, 코드 리팩토링

This commit is contained in:
2026-02-14 18:03:13 +09:00
parent 4fd0aa91bc
commit 9dbf6e6791
15 changed files with 1452 additions and 847 deletions

View File

@@ -484,31 +484,174 @@ class KISClient:
self.ensure_token()
url = f"{self.base_url}/uapi/domestic-stock/v1/quotations/inquire-investor"
headers = self._get_headers(tr_id="FHKST01010900")
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": ticker
}
try:
res = requests.get(url, headers=headers, params=params)
res.raise_for_status()
data = res.json()
if data['rt_cd'] != '0':
return None
# output 리스트: [ {stck_bsop_date: 날짜, frgn_ntby_qty: 외인순매수, orgn_ntby_qty: 기관순매수, ...}, ... ]
trends = []
for item in data['output'][:5]: # 최근 5일치만
for item in data['output'][:5]:
trends.append({
"date": item['stck_bsop_date'],
"foreigner": self._safe_int(item.get('frgn_ntby_qty')), # 외인 순매수량
"institutional": self._safe_int(item.get('orgn_ntby_qty')), # 기관 순매수량
"price_change": float(item['prdy_vrss']) # 전일대비 등락금액
"foreigner": self._safe_int(item.get('frgn_ntby_qty')),
"institutional": self._safe_int(item.get('orgn_ntby_qty')),
"price_change": float(item['prdy_vrss'])
})
# 최근일이 0번 인덱스임
return trends
except Exception as e:
print(f" 투자자 동향 조회 실패({ticker}): {e}")
print(f"[KIS] 투자자 동향 조회 실패({ticker}): {e}")
return None
class KISAsyncClient:
"""
비동기 KIS API 클라이언트
- aiohttp 기반 HTTP 호출
- 동기 KISClient의 토큰/설정을 공유
- 다중 종목 병렬 수집용
"""
def __init__(self, sync_client):
self.sync = sync_client
self.min_interval = 0.5 # 초당 2회 제한
async def _async_get(self, session, url, headers, params):
"""비동기 GET 요청"""
try:
async with session.get(url, headers=headers, params=params) as resp:
return await resp.json()
except Exception as e:
print(f"[KIS Async] Request failed: {e}")
return None
async def get_daily_price_async(self, ticker):
"""비동기 일별 시세 조회"""
import aiohttp
import asyncio
self.sync.ensure_token()
url = f"{self.sync.base_url}/uapi/domestic-stock/v1/quotations/inquire-daily-price"
headers = self.sync._get_headers(tr_id="FHKST01010400")
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": ticker,
"FID_PERIOD_DIV_CODE": "D",
"FID_ORG_ADJ_PRC": "1"
}
async with aiohttp.ClientSession() as session:
data = await self._async_get(session, url, headers, params)
if data and data.get('rt_cd') == '0':
prices = [int(item['stck_clpr']) for item in data['output']]
prices.reverse()
return prices
return []
async def get_investor_trend_async(self, ticker):
"""비동기 투자자 동향 조회"""
import aiohttp
self.sync.ensure_token()
url = f"{self.sync.base_url}/uapi/domestic-stock/v1/quotations/inquire-investor"
headers = self.sync._get_headers(tr_id="FHKST01010900")
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": ticker
}
async with aiohttp.ClientSession() as session:
data = await self._async_get(session, url, headers, params)
if data and data.get('rt_cd') == '0':
trends = []
for item in data['output'][:5]:
trends.append({
"date": item['stck_bsop_date'],
"foreigner": self.sync._safe_int(item.get('frgn_ntby_qty')),
"institutional": self.sync._safe_int(item.get('orgn_ntby_qty')),
"price_change": float(item['prdy_vrss'])
})
return trends
return None
async def get_daily_prices_batch(self, tickers):
"""여러 종목의 일별 시세를 병렬로 조회"""
import aiohttp
import asyncio
self.sync.ensure_token()
results = {}
async with aiohttp.ClientSession() as session:
tasks = []
for i, ticker in enumerate(tickers):
# rate limit: 0.5초 간격으로 요청 생성
if i > 0:
await asyncio.sleep(self.min_interval)
url = f"{self.sync.base_url}/uapi/domestic-stock/v1/quotations/inquire-daily-price"
headers = self.sync._get_headers(tr_id="FHKST01010400")
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": ticker,
"FID_PERIOD_DIV_CODE": "D",
"FID_ORG_ADJ_PRC": "1"
}
tasks.append((ticker, self._async_get(session, url, headers, params)))
for ticker, task in tasks:
data = await task
if data and data.get('rt_cd') == '0':
prices = [int(item['stck_clpr']) for item in data['output']]
prices.reverse()
results[ticker] = prices
else:
results[ticker] = []
return results
async def get_investor_trends_batch(self, tickers):
"""여러 종목의 투자자 동향을 병렬로 조회"""
import aiohttp
import asyncio
self.sync.ensure_token()
results = {}
async with aiohttp.ClientSession() as session:
tasks = []
for i, ticker in enumerate(tickers):
if i > 0:
await asyncio.sleep(self.min_interval)
url = f"{self.sync.base_url}/uapi/domestic-stock/v1/quotations/inquire-investor"
headers = self.sync._get_headers(tr_id="FHKST01010900")
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": ticker
}
tasks.append((ticker, self._async_get(session, url, headers, params)))
for ticker, task in tasks:
data = await task
if data and data.get('rt_cd') == '0':
trends = []
for item in data['output'][:5]:
trends.append({
"date": item['stck_bsop_date'],
"foreigner": self.sync._safe_int(item.get('frgn_ntby_qty')),
"institutional": self.sync._safe_int(item.get('orgn_ntby_qty')),
"price_change": float(item['prdy_vrss'])
})
results[ticker] = trends
else:
results[ticker] = None
return results