feat: 매도 시 같은 그룹 2계좌 동시 매도 (카드 1장+PIN 1개, 선택 팝업)

- orders/handler.py: propose_trade_multi (레그별 독립 검증, 같은 소유자 그룹만,
  SELL+LIMIT/MARKET 한정) + submit_with_pin multi 분기 _submit_multi_legs
  (레그 독립 제출·독립 fill_watcher, 한 레그 실패해도 나머지 시도)
- orders/card.py: format_card_multi (레그별 계좌·수량·예상회수 + 합계)
- behive_web.py: POST /api/order/propose_multi (PIN은 iMessage '매도(2계좌)'),
  sell-choice-modal (두 계좌 모두 / 선택 계좌만 / 취소), doPropose SELL 분기
  (accStatus로 sibling 보유 감지 → 팝업, sibling 수량은 보유 전량)
- fix: /api/order/cancel 응답이 dict를 PendingCard로 취급해 매번 500 나던
  기존 버그 (취소 자체는 동작, 응답만 깨짐)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hyowons
2026-06-12 15:05:35 +09:00
parent 72b98409f2
commit e95e2b3f3a
3 changed files with 371 additions and 7 deletions
@@ -153,6 +153,47 @@ def format_card(request: dict, market_data: dict, card_id: str,
return '\n'.join(lines)
def format_card_multi(legs: list, side: str, symbol: str, symbol_name: str,
order_type: str, price: Optional[int], card_id: str,
state_warning: Optional[str] = None) -> str:
"""복수 계좌 동시 주문 카드 — 레그별 계좌·수량·예상회수 나열.
legs: [{'account': str, 'qty': int, 'position_qty': int?}, ...]
"""
cfg = _limits()['card']
side_word = '매수' if side == 'BUY' else '매도'
side_emoji = cfg['buy_emoji'] if side == 'BUY' else cfg['sell_emoji']
marker = cfg['highlight_marker']
spouse_marker = ' 🔐 가희 계좌' if any(_is_spouse(l['account']) for l in legs) else ''
type_marker = f' {cfg["warning_emoji"]} 시장가' if order_type == 'MARKET' else ''
lines = [f'{side_emoji} {_md_bold(side_word + " 미리보기")} [#{card_id}]{type_marker}{spouse_marker}'
f' · 계좌 {len(legs)}개 동시', '']
lines.append(f'{marker} {_md_bold(side_word)}')
lines.append(f'{marker} 종목: {_md_bold(symbol_name)} ({symbol})')
if state_warning:
lines.append(state_warning)
if order_type == 'LIMIT':
lines.append(f'{marker} 가격: {_md_bold(_money(price))}')
else:
lines.append(f'{marker} 가격: {_md_bold("시장가")}')
lines.append('')
total = 0
for l in legs:
part = f'{marker} {_md_bold(_account_display(l["account"]))}: {l["qty"]:,}'
if l.get('position_qty'):
part += f' (보유 {l["position_qty"]:,}주)'
if order_type == 'LIMIT' and price:
part += f'{_money(price * l["qty"])}'
total += price * l['qty']
lines.append(part)
if order_type == 'LIMIT' and price:
money_label = '예상 금액 합계' if side == 'BUY' else '예상 회수 합계'
lines.append(f'{money_label}: {_money(total)}')
lines.append(f'{_limits()["pin"]["expiry_seconds"]}초 후 만료')
return '\n'.join(lines)
def format_pin_message(pin: str) -> str:
return pin