feat: 외부 유입 포지션 ActiveTrade 자동 등록 — 손절·분할익절 관리 편입
다른 인스턴스/증권사 앱에서 매수돼 동기화로 들어온 포지션은 ActiveTrade 행이 없어 손절·분할익절·트레일링 관리를 전혀 받지 못했다(2026-09-22 카카오뱅크가 -13%까지 방치되다 청산). 동기화 시 보유 중인데 active 행이 없으면 평균단가를 진입가로 등록하고 손절가를 STOP_LOSS_PERCENT 기준으로 설정한다. 데이트레이딩 태깅 종목은 제외 (DT 엔진이 당일 청산 대상으로 복원해버리므로). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -799,6 +799,7 @@ async def sync_portfolio(db: AsyncSession = Depends(get_db), force: bool = True)
|
||||
# ActiveTrade 잔량 정합 — 같은 종목에 active 행이 1개뿐일 때만 실제 보유량으로 교정.
|
||||
# (분할매도·수동매도 누락으로 DB 잔량이 실제와 달라지면 다음 청산이 과다매도)
|
||||
try:
|
||||
from app.core.config import settings as _cfg # 모듈 상단 미임포트(함수 지역 사용)
|
||||
_held = {h.stock_code: h.quantity for h in new_holdings if h.quantity > 0}
|
||||
_at_rows = (await db.execute(
|
||||
select(ActiveTrade).where(ActiveTrade.status == "active")
|
||||
@@ -819,6 +820,41 @@ async def sync_portfolio(db: AsyncSession = Depends(get_db), force: bool = True)
|
||||
f"🧹 ActiveTrade 잔량 교정: {_code} {_row.quantity} → {_actual}"
|
||||
)
|
||||
_row.quantity = _actual
|
||||
|
||||
# 외부 유입 포지션(다른 인스턴스/증권사 앱 매수 등)은 ActiveTrade 행이 없어
|
||||
# 손절·분할익절·트레일링 관리를 전혀 받지 못한다(2026-09-22 카카오뱅크 -13%).
|
||||
# 보유 중인데 active 행이 없으면 현재 평균단가를 진입가로 보고 생성한다.
|
||||
# 데이트레이딩 태깅 종목은 제외 — 엔진이 당일 청산 대상으로 복원해버린다.
|
||||
_managed = set(_by_code)
|
||||
for _h in new_holdings:
|
||||
if _h.quantity <= 0 or _h.stock_code in _managed:
|
||||
continue
|
||||
if (_h.strategy_type or "swing") == "daytrading":
|
||||
continue
|
||||
_avg = float(_h.average_price or 0)
|
||||
if _avg <= 0:
|
||||
continue
|
||||
_cur = float(_h.current_price or _avg)
|
||||
db.add(ActiveTrade(
|
||||
user_id=getattr(_h, "user_id", None),
|
||||
stock_code=_h.stock_code,
|
||||
stock_name=_h.stock_name,
|
||||
quantity=_h.quantity,
|
||||
entry_price=Decimal(str(_avg)),
|
||||
current_price=Decimal(str(_cur)),
|
||||
stop_loss=Decimal(str(round(_avg * (1 - abs(_cfg.STOP_LOSS_PERCENT) / 100), 2))),
|
||||
status="active",
|
||||
remaining_pct=Decimal("1.0"),
|
||||
highest_since_entry=Decimal(str(max(_avg, _cur))),
|
||||
strategy_name=_h.strategy_type or "swing",
|
||||
entry_reason="외부 유입 포지션 — 동기화 시 자동 등록(진입가=평균단가)",
|
||||
currency_code=_h.currency_code or "KRW",
|
||||
exchange_code=_h.exchange_code,
|
||||
))
|
||||
logger.info(
|
||||
f"🆕 ActiveTrade 자동 등록: {_h.stock_name}({_h.stock_code}) "
|
||||
f"{_h.quantity}주 @ {_avg:,.0f} — 외부 유입 포지션"
|
||||
)
|
||||
except Exception as _rec_err:
|
||||
logger.warning(f"ActiveTrade 정합 실패(무시): {_rec_err}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user