- 스윙 체결가 확인 전 executed_price NULL + fill_sweep, 진입가 resolve(ActiveTrade↔Portfolio), 0체결/부분체결 매수 장부 원복 - DT 고아 감지(근거 한정)·0체결 매도 복원·stale 점검 확대, 시간손절 -0.3% 원복 - 폴백 손절 ATR 캐시, 거래량부족-차단 신호 매수 차단, 현금 5% 유보 재적용, MDD peak/reset 수정 - KIS 서킷브레이커 청산 우회·경보, db_worker 재큐잉 순서, heartbeat·health-watch 사이드카 - 실시간 WS 재구독·41건 우선순위·다건 프레임 분할, 차트 샘플 제거·당일봉 합성, WS 서브프로토콜 인증 - 프론트: 미관리 그룹·확인 모달·차트 실시간 반영·예수금 표시 정정 - 전문가 에이전트 6종 추가(.claude/agents) - 유닛 스위트 2667 passed (소켓 차단 하), tsc OK Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y83Za98XsNw9zuA4FTUytN
53 lines
2.3 KiB
Python
53 lines
2.3 KiB
Python
"""[하드닝 R3 2026-10-09] 기존 스윙 ActiveTrade.entry_price 1회성 백필 (주문가 → 체결가).
|
|
|
|
배포 후 1회 실행:
|
|
docker compose exec backend python backfill_swing_entry_fill.py # dry-run(기본, SELECT 만)
|
|
docker compose exec backend python backfill_swing_entry_fill.py --apply # 실제 갱신
|
|
|
|
- 대상 선정·계산은 `trade_executor.plan_swing_entry_backfill_v2`(순수 함수) 가 한다.
|
|
체결가가 확정된 스윙 BUY 와 1:1(분할매수면 체결수량 합 = 행 수량)로 대응되고, 현재 진입가가
|
|
주문가 기준 그대로인 행만 보정한다. 그 외(시딩·DT·레거시 미확인·부분체결 등)는 사유만 출력한다.
|
|
- 쓰기는 db_worker 의 `upsert_active_trade` 핸들러(`_execute_db_task_impl`) 를 그대로 쓴다.
|
|
이 스크립트는 앱 밖 프로세스라 앱의 워커 큐에 넣을 수 없으므로 같은 핸들러를 직접 실행한다.
|
|
- 스윙 사이클과 같은 행을 동시에 갱신하지 않도록 **장 마감 후** 실행을 권장한다.
|
|
"""
|
|
import argparse
|
|
import asyncio
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
|
|
from app.core.database import AsyncSessionLocal # noqa: E402
|
|
from app.services.trade_executor import ( # noqa: E402
|
|
apply_swing_entry_backfill_v2,
|
|
collect_swing_entry_backfill_v2,
|
|
)
|
|
|
|
|
|
async def _submit_direct(payload: dict) -> None:
|
|
from app.services.db_worker import _execute_db_task_impl
|
|
await _execute_db_task_impl({"type": "upsert_active_trade", "data": payload})
|
|
|
|
|
|
async def main(apply: bool) -> int:
|
|
async with AsyncSessionLocal() as db:
|
|
plans = await collect_swing_entry_backfill_v2(db)
|
|
for p in plans:
|
|
mark = "FIX " if p["payload"] else "SKIP"
|
|
print(f"{mark} id={p['active_id']} {p['stock_code']} [{p['strategy_name']}] "
|
|
f"entry={p['entry_price']} — {p['reason']}")
|
|
todo = sum(1 for p in plans if p["payload"])
|
|
if not apply:
|
|
print(f"dry-run: 보정 대상 {todo}건 (--apply 로 실행)")
|
|
return 0
|
|
n = await apply_swing_entry_backfill_v2(plans, submit=_submit_direct)
|
|
print(f"applied: {n}건")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--apply", action="store_true")
|
|
sys.exit(asyncio.run(main(ap.parse_args().apply)))
|