보유종목 1분마다 호출하는부분 수정

This commit is contained in:
2026-02-27 12:19:48 +09:00
parent a47591e40d
commit 6f23b06816
10 changed files with 729 additions and 353 deletions

View File

@@ -101,10 +101,17 @@ _last_reset_date: Optional[date] = None
# 매매 실행 lock — _positions/_daily_pnl/_closed_today 변경 시 반드시 획득
_trade_lock = asyncio.Lock()
# 포트폴리오 요약 캐시 (get_balance() 호출 최소화)
_portfolio_summary_cache: Dict[str, float] = {}
_portfolio_summary_ts: Optional[datetime] = None
_portfolio_cache_dirty: bool = True # 최초 또는 매매 발생 시 강제 갱신
_PORTFOLIO_CACHE_TTL: float = 300.0 # 매매 없을 때 최대 5분 캐시 유지
def _reset_if_new_day() -> None:
"""날짜가 바뀌면 일일 상태 초기화"""
global _daily_pnl, _orb_cache, _last_reset_date, _last_total_assets, _closed_today
global _portfolio_summary_cache, _portfolio_summary_ts, _portfolio_cache_dirty
today = _today_kst()
if _last_reset_date != today:
_daily_pnl = 0.0
@@ -112,6 +119,9 @@ def _reset_if_new_day() -> None:
_last_total_assets = 0.0
_closed_today = set()
_last_reset_date = today
_portfolio_summary_cache = {}
_portfolio_summary_ts = None
_portfolio_cache_dirty = True
logger.info(f"📅 데이트레이딩 상태 일일 초기화: {today}")
@@ -143,18 +153,39 @@ async def _get_current_price(stock_code: str) -> float:
return 0.0
async def _get_portfolio_summary() -> Dict[str, float]:
"""포트폴리오 요약 (총자산, 가용 현금)"""
async def _get_portfolio_summary(force: bool = False) -> Dict[str, float]:
"""포트폴리오 요약 (총자산, 가용 현금).
매매가 발생했거나 캐시 TTL이 만료된 경우에만 KIS get_balance() 호출.
그 외에는 인메모리 캐시 반환 (API 호출 최소화).
"""
global _portfolio_summary_cache, _portfolio_summary_ts, _portfolio_cache_dirty
now = _kst_now()
cache_age = (now - _portfolio_summary_ts).total_seconds() if _portfolio_summary_ts else 9999.0
cache_valid = (
not _portfolio_cache_dirty
and cache_age < _PORTFOLIO_CACHE_TTL
and bool(_portfolio_summary_cache)
)
if not force and cache_valid:
logger.debug(f"⏭️ Portfolio summary cache hit (age={cache_age:.0f}s) — skipping get_balance()")
return _portfolio_summary_cache
try:
balance = await korea_investment_api.get_balance()
output2 = balance.get("output2", [{}])
summary = output2[0] if output2 else {}
total_assets = float(summary.get("tot_evlu_amt", 0) or 0)
available_cash = float(summary.get("prvs_rcdl_excc_amt", 0) or 0)
return {"total_assets": total_assets, "available_cash": available_cash}
_portfolio_summary_cache = {"total_assets": total_assets, "available_cash": available_cash}
_portfolio_summary_ts = now
_portfolio_cache_dirty = False
return _portfolio_summary_cache
except Exception as e:
logger.error(f"포트폴리오 조회 실패: {e}")
return {"total_assets": 0.0, "available_cash": 0.0}
return _portfolio_summary_cache or {"total_assets": 0.0, "available_cash": 0.0}
async def _place_buy_order(
@@ -412,6 +443,7 @@ async def _check_exits(
ok = await _place_sell_order(code, quantity, price=0) # 시장가
if ok:
pnl = (current_price - pos["entry_price"]) * quantity
_portfolio_cache_dirty = True # 매도 체결 → 잔고 변경됨
async with _trade_lock:
_daily_pnl += pnl
_positions.pop(code, None)
@@ -580,6 +612,9 @@ async def _execute_entry(signal: DayTradeSignal, quantity: int) -> Optional[Dict
if not order_no:
return None
global _portfolio_cache_dirty
_portfolio_cache_dirty = True # 매수 체결 → 잔고 변경됨
_positions[signal.stock_code] = {
"stock_name": signal.stock_name,
"strategy": signal.strategy,
@@ -643,6 +678,7 @@ async def _execute_eod_flat() -> None:
ok = await _place_sell_order(code, pos["quantity"], price=0)
if ok:
pnl = (current_price - pos["entry_price"]) * pos["quantity"]
_portfolio_cache_dirty = True # EOD 청산 → 잔고 변경됨
async with _trade_lock:
_daily_pnl += pnl
_positions.pop(code, None)

View File

@@ -606,7 +606,13 @@ async def check_and_execute_exit_trades(db: AsyncSession):
from datetime import datetime
try:
await sync_portfolio(db, force=True)
# 매매가 발생했거나 캐시가 비어있을 때만 KIS 잔고 조회 (API 호출 최소화)
latest_trade_id = await _get_latest_executed_trade_id(db)
if latest_trade_id != portfolio_cache.last_trade_id or not portfolio_cache.holdings:
logger.info(f"🔄 New trade detected (prev={portfolio_cache.last_trade_id}, cur={latest_trade_id}) — syncing balance")
await sync_portfolio(db, force=True)
else:
logger.debug("⏭️ No new trade since last sync — skipping get_balance()")
# MDD 업데이트 (async — thread-safe lock 내부)
summary = await _get_portfolio_summary(db)