Compare commits

..

3 Commits

Author SHA1 Message Date
6a6d3bd631 Merge pull request 'fix: market_data에 unrealized_pnl_pct/holding_days 추가하여 SELL 시나리오 정상화 (#259)' (#263) from feature/issue-259-market-data-pnl-holding-days into main
Some checks failed
CI / test (push) Has been cancelled
Reviewed-on: #263
2026-02-26 00:23:55 +09:00
agentson
7aa5fedc12 fix: market_data에 unrealized_pnl_pct/holding_days 추가하여 SELL 시나리오 정상화 (#259)
Some checks failed
CI / test (pull_request) Has been cancelled
trading_cycle()의 market_data에 보유 포지션 정보가 없어
Condition requires 'unrealized_pnl_pct' but key missing from market_data 경고 발생.
보유 종목(NVDA 등)의 take-profit/stop-loss 시나리오가 평가 불가하여 HOLD(confidence=0) 고착.

- get_open_position()에 timestamp 컬럼 추가
- market_data 구성 시 open_position 조회 후 아래 키 추가:
  - unrealized_pnl_pct: (current_price - entry_price) / entry_price * 100
  - holding_days: 매수일로부터 경과 일수

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 00:23:28 +09:00
agentson
3e777a5ab8 fix: mock_settings에 MODE='paper' 명시하여 paper 모드 테스트 실패 수정 (#261)
mock_settings fixture에 MODE 미지정 시 .env의 MODE=live가 적용되어
paper TR_ID를 검증하는 테스트 3개가 실패.

- test_buy_market_order: VTTT1002U 기대 → TTTT1002U 실제
- test_sell_limit_order: VTTT1001U 기대 → TTTT1006U 실제
- test_us_paper_uses_vttt1004u: VTTT1004U 기대 → TTTT1004U 실제

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 00:23:01 +09:00
2 changed files with 18 additions and 2 deletions

View File

@@ -254,7 +254,7 @@ def get_open_position(
"""Return open position if latest trade is BUY, else None.""" """Return open position if latest trade is BUY, else None."""
cursor = conn.execute( cursor = conn.execute(
""" """
SELECT action, decision_id, price, quantity SELECT action, decision_id, price, quantity, timestamp
FROM trades FROM trades
WHERE stock_code = ? WHERE stock_code = ?
AND market = ? AND market = ?
@@ -266,7 +266,7 @@ def get_open_position(
row = cursor.fetchone() row = cursor.fetchone()
if not row or row[0] != "BUY": if not row or row[0] != "BUY":
return None return None
return {"decision_id": row[1], "price": row[2], "quantity": row[3]} return {"decision_id": row[1], "price": row[2], "quantity": row[3], "timestamp": row[4]}
def get_recent_symbols( def get_recent_symbols(

View File

@@ -576,6 +576,22 @@ async def trading_cycle(
market_data["rsi"] = candidate.rsi market_data["rsi"] = candidate.rsi
market_data["volume_ratio"] = candidate.volume_ratio market_data["volume_ratio"] = candidate.volume_ratio
# Enrich market_data with holding info for SELL/HOLD scenario conditions
open_pos = get_open_position(db_conn, stock_code, market.code)
if open_pos and current_price > 0:
entry_price = safe_float(open_pos.get("price"), 0.0)
if entry_price > 0:
market_data["unrealized_pnl_pct"] = (
(current_price - entry_price) / entry_price * 100
)
entry_ts = open_pos.get("timestamp")
if entry_ts:
try:
entry_date = datetime.fromisoformat(entry_ts).date()
market_data["holding_days"] = (datetime.now(UTC).date() - entry_date).days
except (ValueError, TypeError):
pass
# 1.3. Record L7 real-time context (market-scoped keys) # 1.3. Record L7 real-time context (market-scoped keys)
timeframe = datetime.now(UTC).isoformat() timeframe = datetime.now(UTC).isoformat()
context_store.set_context( context_store.set_context(