fix(P1): MDD 축소 수량 반영·ActiveTrade 전략명 필수화·스윙 매도에서 DT 보유분 제외
- mdd_manager: 강제 축소가 TradeHistory/remaining_pct 만 갱신하고 실제 수량은 어디도 줄이지 않아 다음 청산이 축소 전 수량으로 매도했음 → _reflect_reduction()으로 Portfolio·DT 인메모리 반영, ActiveTrade.quantity 도 잔량으로 갱신 (두 경로 모두) - swing/exit_service: close/upsert 에 strategy_name 미전달 시 db_worker 가 최신 active 행(=DT)을 닫거나 트레일링 고점을 덮어씀 → 항상 전략명 명시 - trade_executor._execute_sell: Portfolio 행(스윙+DT 합계)을 그대로 매도 대상으로 삼아 DT 보유분까지 팔았음 → 엔진 보유 수량을 제외한 자기 전략 보유분만 대상, upsert 에 전략명 - portfolio.sync: DT 태깅이 해외 루프에만 적용돼 국내 DT 매수분이 swing 으로 저장되면 스윙 엔진이 손절 → 국내에도 적용. _dt_qty 정의를 output1 블록 밖으로 올려 NameError 방지 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -521,6 +521,18 @@ async def sync_portfolio(db: AsyncSession = Depends(get_db), force: bool = True)
|
||||
# 디버깅: KIS API 전체 응답 로깅
|
||||
logger.info(f"🔍 KIS API balance response keys: {balance_data.keys() if balance_data else 'None'}")
|
||||
|
||||
# 데이트레이딩 엔진이 보유 중인 종목 → strategy_type='daytrading' 태깅용.
|
||||
# (국내 output1 블록 안에서 정의하면 해외 루프에서 NameError → sync 전체 실패)
|
||||
try:
|
||||
from app.services.daytrading.position_manager import day_trading_state as _dts
|
||||
_dt_qty = {
|
||||
code: int(pos.get("quantity", 0) or 0)
|
||||
for code, pos in _dts.positions.items()
|
||||
}
|
||||
except Exception:
|
||||
_dt_qty = {}
|
||||
_dt_codes = set(_dt_qty)
|
||||
|
||||
# Balance API 오류 시 기존 DB 보유종목을 삭제하지 않고 캐시 유지
|
||||
if not balance_data or balance_data.get('rt_cd') != '0':
|
||||
error_msg = balance_data.get('msg1', 'Unknown error') if balance_data else 'No response'
|
||||
@@ -609,17 +621,6 @@ async def sync_portfolio(db: AsyncSession = Depends(get_db), force: bool = True)
|
||||
_realtime_price_map = {}
|
||||
# ──────────────────────────────────────────────────────────────────
|
||||
|
||||
# 데이트레이딩 엔진이 보유 중인 종목 → strategy_type='daytrading' 태깅용
|
||||
try:
|
||||
from app.services.daytrading.position_manager import day_trading_state as _dts
|
||||
_dt_qty = {
|
||||
code: int(pos.get("quantity", 0) or 0)
|
||||
for code, pos in _dts.positions.items()
|
||||
}
|
||||
_dt_codes = set(_dt_qty)
|
||||
except Exception:
|
||||
_dt_qty, _dt_codes = {}, set()
|
||||
|
||||
for item in balance_data["output1"]:
|
||||
stock_code = item.get("pdno")
|
||||
|
||||
@@ -682,6 +683,9 @@ async def sync_portfolio(db: AsyncSession = Depends(get_db), force: bool = True)
|
||||
h.profit_loss = profit_loss
|
||||
h.profit_loss_rate = profit_loss_rate
|
||||
h.market = "KRX"
|
||||
# 전량이 DT 포지션일 때만 태깅(혼재 시 스윙 청산 대상 유지)
|
||||
if stock_code in _dt_codes and quantity <= _dt_qty.get(stock_code, 0):
|
||||
h.strategy_type = "daytrading"
|
||||
new_holdings.append(h)
|
||||
else:
|
||||
new_holding = Portfolio(
|
||||
@@ -693,6 +697,11 @@ async def sync_portfolio(db: AsyncSession = Depends(get_db), force: bool = True)
|
||||
profit_loss=profit_loss,
|
||||
profit_loss_rate=profit_loss_rate,
|
||||
market="KRX",
|
||||
strategy_type=(
|
||||
"daytrading"
|
||||
if quantity <= _dt_qty.get(stock_code, 0)
|
||||
else "swing"
|
||||
),
|
||||
)
|
||||
db.add(new_holding)
|
||||
new_holdings.append(new_holding)
|
||||
|
||||
Reference in New Issue
Block a user