feat: 대시보드 오픈 포지션 패널 추가 (#193) #194

Merged
jihoson merged 1 commits from feature/issue-193-dashboard-positions into main 2026-02-21 21:07:53 +09:00
Collaborator

Summary

  • /api/positions 엔드포인트 신설: 각 종목의 마지막 거래가 BUY인 경우 오픈 포지션으로 반환
  • 대시보드 카드 영역 아래, P&L 차트 위에 포지션 패널 삽입
  • _connect()에 WAL 모드 + busy_timeout=8000 추가 → 트레이딩 루프 쓰기와 대시보드 읽기 동시 안전
  • init_db()에 idx_trades_stock_market_ts 인덱스 추가 → 포지션 윈도우 쿼리 최적화

변경 파일

  • src/dashboard/app.py: /api/positions 엔드포인트, WAL 모드
  • src/dashboard/static/index.html: 포지션 패널 CSS + HTML + JS
  • src/db.py: idx_trades_stock_market_ts 인덱스
  • tests/test_dashboard.py: 포지션 테스트 3개 추가

Test plan

  • test_positions_returns_open_buy — BUY 포지션 정상 반환
  • test_positions_excludes_closed_sell — 마지막 SELL 종목 제외
  • test_positions_empty_when_no_trades — 빈 DB 처리
  • 전체 736 테스트 통과
  • 커버리지 77% 유지

Closes #193

Generated with Claude Code

## Summary - /api/positions 엔드포인트 신설: 각 종목의 마지막 거래가 BUY인 경우 오픈 포지션으로 반환 - 대시보드 카드 영역 아래, P&L 차트 위에 포지션 패널 삽입 - _connect()에 WAL 모드 + busy_timeout=8000 추가 → 트레이딩 루프 쓰기와 대시보드 읽기 동시 안전 - init_db()에 idx_trades_stock_market_ts 인덱스 추가 → 포지션 윈도우 쿼리 최적화 ## 변경 파일 - src/dashboard/app.py: /api/positions 엔드포인트, WAL 모드 - src/dashboard/static/index.html: 포지션 패널 CSS + HTML + JS - src/db.py: idx_trades_stock_market_ts 인덱스 - tests/test_dashboard.py: 포지션 테스트 3개 추가 ## Test plan - [x] test_positions_returns_open_buy — BUY 포지션 정상 반환 - [x] test_positions_excludes_closed_sell — 마지막 SELL 종목 제외 - [x] test_positions_empty_when_no_trades — 빈 DB 처리 - [x] 전체 736 테스트 통과 - [x] 커버리지 77% 유지 Closes #193 Generated with Claude Code
agentson added 1 commit 2026-02-21 20:53:09 +09:00
feat: 대시보드 오픈 포지션 패널 추가 (#193)
Some checks failed
CI / test (pull_request) Has been cancelled
40ea41cf3c
- /api/positions 엔드포인트 신설: 마지막 거래가 BUY인 종목을 오픈 포지션으로 반환
- _connect()에 WAL 모드 + busy_timeout=8000 추가 (트레이딩 루프와 동시 읽기 안전)
- init_db()에 idx_trades_stock_market_ts 인덱스 추가 (포지션 쿼리 최적화)
- index.html: 카드와 P&L 차트 사이에 포지션 패널 삽입 (종목/시장/수량/진입가/보유시간)
- 포지션 패널 테스트 3개 추가 (open BUY 반환, SELL 제외, 빈 DB 처리)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Owner

sell 종목이라해도 잔량이 남아 있을 수 있잖아. 오픈 포지션이 어떤건지 설명해줘.

sell 종목이라해도 잔량이 남아 있을 수 있잖아. 오픈 포지션이 어떤건지 설명해줘.
Author
Collaborator

좋은 지적입니다. 현재 시스템의 "오픈 포지션" 정의를 설명드리겠습니다.

현재 시스템의 오픈 포지션 정의

src/db.py:226get_open_position() 구현 기준:

마지막 거래가 BUY인 종목 = 오픈 포지션

SELECT action, price, quantity FROM trades
WHERE stock_code = ? AND market = ?
ORDER BY timestamp DESC LIMIT 1
-- 결과가 BUY이면 포지션 있음, SELL이면 포지션 없음

이 방식이 성립하는 이유

현재 Ouroboros의 매매 구조는 전량 매수 / 전량 매도 방식입니다.

  • BUY: 가용 자금의 일정 비율로 전량 매수
  • SELL: 해당 종목 전량 매도
  • 부분 매도(partial sell) 기능 없음

따라서 마지막 거래가 SELL이면 잔량 = 0, BUY이면 잔량 = BUY 시 수량 전체입니다.

한계 인정

유저 지적처럼, 향후 부분 매도 기능이 추가된다면 이 방식은 부정확해집니다.
그 경우에는 SUM(CASE WHEN action=BUY THEN quantity ELSE -quantity END) > 0 방식으로 전환이 필요합니다.

현재 범위(전량 매수/매도 시스템)에서는 이 구현이 정확하게 동작합니다.

좋은 지적입니다. 현재 시스템의 "오픈 포지션" 정의를 설명드리겠습니다. ## 현재 시스템의 오픈 포지션 정의 `src/db.py:226`의 `get_open_position()` 구현 기준: > **마지막 거래가 BUY인 종목 = 오픈 포지션** ```sql SELECT action, price, quantity FROM trades WHERE stock_code = ? AND market = ? ORDER BY timestamp DESC LIMIT 1 -- 결과가 BUY이면 포지션 있음, SELL이면 포지션 없음 ``` ## 이 방식이 성립하는 이유 현재 Ouroboros의 매매 구조는 **전량 매수 / 전량 매도** 방식입니다. - BUY: 가용 자금의 일정 비율로 전량 매수 - SELL: 해당 종목 전량 매도 - 부분 매도(partial sell) 기능 없음 따라서 마지막 거래가 SELL이면 잔량 = 0, BUY이면 잔량 = BUY 시 수량 전체입니다. ## 한계 인정 유저 지적처럼, 향후 **부분 매도** 기능이 추가된다면 이 방식은 부정확해집니다. 그 경우에는 `SUM(CASE WHEN action=BUY THEN quantity ELSE -quantity END) > 0` 방식으로 전환이 필요합니다. 현재 범위(전량 매수/매도 시스템)에서는 이 구현이 정확하게 동작합니다.
Owner

전량 매도라 하더라도 매도 주문인거지 체결은 아니지 않나? 시장가 매도가 아닌한 잔량은 남을 수 있을텐데?

전량 매도라 하더라도 매도 주문인거지 체결은 아니지 않나? 시장가 매도가 아닌한 잔량은 남을 수 있을텐데?
Author
Collaborator

맞습니다. 코드를 확인한 결과 이 부분이 실제 한계입니다.

국내주식 (src/main.py:708): price=0 → 시장가(ORD_DVSN=01) → 즉시 전량 체결 보장

해외주식 (src/main.py:714-730): KIS VTS가 시장가를 거부하므로 지정가 사용. price=current_price → ORD_DVSN=00 → 미체결 가능

현재 구현의 한계: trades 테이블은 주문 접수 성공(rt_cd=0) 시점에 기록됩니다(src/main.py:781). 체결 여부와 무관합니다.

따라서 해외주식에서 SELL 지정가 주문이 미체결되면, trades에는 SELL이 기록되지만 실제로는 포지션이 남아있는 상태가 됩니다. /api/positions는 "마지막=SELL이므로 포지션 없음"으로 잘못 판단합니다.

국내주식은 시장가이므로 이 문제가 없지만, 해외주식은 지적하신 대로 포지션 과소 표시 가능성이 있습니다.

근본적 해결: KIS 잔고 조회 API를 주기적으로 폴링하여 실제 체결 잔고와 동기화해야 합니다. 현재 스코프 밖이므로 별도 이슈로 추적하겠습니다.

맞습니다. 코드를 확인한 결과 이 부분이 실제 한계입니다. **국내주식** (src/main.py:708): price=0 → 시장가(ORD_DVSN=01) → 즉시 전량 체결 보장 **해외주식** (src/main.py:714-730): KIS VTS가 시장가를 거부하므로 지정가 사용. price=current_price → ORD_DVSN=00 → 미체결 가능 **현재 구현의 한계**: trades 테이블은 주문 접수 성공(rt_cd=0) 시점에 기록됩니다(src/main.py:781). 체결 여부와 무관합니다. 따라서 해외주식에서 SELL 지정가 주문이 미체결되면, trades에는 SELL이 기록되지만 실제로는 포지션이 남아있는 상태가 됩니다. /api/positions는 "마지막=SELL이므로 포지션 없음"으로 잘못 판단합니다. 국내주식은 시장가이므로 이 문제가 없지만, 해외주식은 지적하신 대로 포지션 과소 표시 가능성이 있습니다. **근본적 해결**: KIS 잔고 조회 API를 주기적으로 폴링하여 실제 체결 잔고와 동기화해야 합니다. 현재 스코프 밖이므로 별도 이슈로 추적하겠습니다.
jihoson merged commit 2d5912dc08 into main 2026-02-21 21:07:53 +09:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jihoson/The-Ouroboros#194