Compare commits
2 Commits
8456b00dfa
...
36fcd1ef28
| Author | SHA1 | Date | |
|---|---|---|---|
| 36fcd1ef28 | |||
| 6207d13487 |
@@ -799,6 +799,7 @@ async def sync_portfolio(db: AsyncSession = Depends(get_db), force: bool = True)
|
|||||||
# ActiveTrade 잔량 정합 — 같은 종목에 active 행이 1개뿐일 때만 실제 보유량으로 교정.
|
# ActiveTrade 잔량 정합 — 같은 종목에 active 행이 1개뿐일 때만 실제 보유량으로 교정.
|
||||||
# (분할매도·수동매도 누락으로 DB 잔량이 실제와 달라지면 다음 청산이 과다매도)
|
# (분할매도·수동매도 누락으로 DB 잔량이 실제와 달라지면 다음 청산이 과다매도)
|
||||||
try:
|
try:
|
||||||
|
from app.core.config import settings as _cfg # 모듈 상단 미임포트(함수 지역 사용)
|
||||||
_held = {h.stock_code: h.quantity for h in new_holdings if h.quantity > 0}
|
_held = {h.stock_code: h.quantity for h in new_holdings if h.quantity > 0}
|
||||||
_at_rows = (await db.execute(
|
_at_rows = (await db.execute(
|
||||||
select(ActiveTrade).where(ActiveTrade.status == "active")
|
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}"
|
f"🧹 ActiveTrade 잔량 교정: {_code} {_row.quantity} → {_actual}"
|
||||||
)
|
)
|
||||||
_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:
|
except Exception as _rec_err:
|
||||||
logger.warning(f"ActiveTrade 정합 실패(무시): {_rec_err}")
|
logger.warning(f"ActiveTrade 정합 실패(무시): {_rec_err}")
|
||||||
|
|
||||||
|
|||||||
@@ -154,3 +154,16 @@ def test_active_trade_patch_supports_quantity():
|
|||||||
assert "quantity" in pf.ActiveTradeStatusUpdateRequest.model_fields
|
assert "quantity" in pf.ActiveTradeStatusUpdateRequest.model_fields
|
||||||
src = inspect.getsource(pf.update_active_trade_status)
|
src = inspect.getsource(pf.update_active_trade_status)
|
||||||
assert "active_trade.quantity = request.quantity" in src
|
assert "active_trade.quantity = request.quantity" in src
|
||||||
|
|
||||||
|
|
||||||
|
def test_sync_seeds_active_trade_for_external_positions():
|
||||||
|
"""외부 유입 보유 종목(ActiveTrade 없음)은 동기화 시 자동 등록되어야 손절 관리를 받는다."""
|
||||||
|
import inspect
|
||||||
|
from app.api.endpoints import portfolio as pf
|
||||||
|
src = inspect.getsource(pf.sync_portfolio)
|
||||||
|
assert "외부 유입 포지션 — 동기화 시 자동 등록" in src
|
||||||
|
# 데이트레이딩 태깅 종목은 제외 (엔진이 당일 청산 대상으로 복원해버림)
|
||||||
|
assert '(_h.strategy_type or "swing") == "daytrading"' in src
|
||||||
|
# 평균단가를 진입가로, 손절가는 STOP_LOSS_PERCENT 기준
|
||||||
|
assert "_cfg.STOP_LOSS_PERCENT" in src
|
||||||
|
assert "entry_price=Decimal(str(_avg))" in src
|
||||||
|
|||||||
Reference in New Issue
Block a user