auto: 일일 백업 2026-08-26 02:00
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -75,6 +75,8 @@ def format_card(request: dict, market_data: dict, card_id: str,
|
||||
type_marker = f' {cfg["warning_emoji"]} 스톱지정가(예약)'
|
||||
elif request['order_type'] == 'TRAILING_STOP':
|
||||
type_marker = f' {cfg["warning_emoji"]} 트레일링 스톱(예약)'
|
||||
elif request['order_type'] == 'TRAILING_ARM':
|
||||
type_marker = f' {cfg["warning_emoji"]} 예약 트레일링(발동 대기)'
|
||||
amend_marker = ' ✏️ 수정됨' if amended else ''
|
||||
|
||||
lines = [f'{side_emoji} {_md_bold(side_word + " 미리보기")} [#{card_id}]{type_marker}{spouse_marker}{amend_marker}', '']
|
||||
@@ -98,15 +100,29 @@ def format_card(request: dict, market_data: dict, card_id: str,
|
||||
cur_part = f' (현재가 {_money(cur)})' if cur else ''
|
||||
lines.append(f'{marker} 조건: {_md_bold(_money(stop_p) + " 도달 시")}{cur_part}')
|
||||
lines.append(f'{marker} 매도가격: {_md_bold(_money(request["price"]))} (지정가)')
|
||||
elif request['order_type'] == 'TRAILING_STOP':
|
||||
elif request['order_type'] in ('TRAILING_STOP', 'TRAILING_ARM'):
|
||||
# 승인 대상은 "계단 정의" — 각 계단의 조건단가는 고점이 오르면 제 하락률을 유지하며
|
||||
# 함께 따라 올라간다. 계단이 1개면 종전의 단일 트레일링과 같다.
|
||||
is_arm = request['order_type'] == 'TRAILING_ARM'
|
||||
cur = market_data.get('current_price')
|
||||
cur_part = f' (현재가 {_money(cur)})' if cur else ''
|
||||
# 초기 고점은 항상 등록 시점 현재가 — 과거 고점 기준 옵션은 2026-07-30 제거됨.
|
||||
# 예약 트레일링은 그 자리에 발동가가 들어간다(계단 계산 기준도 발동가).
|
||||
peak_v = request.get('trail_peak') or 0
|
||||
steps = request.get('trail_steps') or []
|
||||
lines.append(f'{marker} 시작 고점: {_md_bold(_money(peak_v))} (등록 시점){cur_part}')
|
||||
if is_arm:
|
||||
gap = ''
|
||||
if cur:
|
||||
gap = f' — 발동까지 {_pct((peak_v - cur) / cur * 100)}'
|
||||
lines.append(f'{marker} 발동가: {_md_bold(_money(peak_v) + " 도달 시 시작")}{cur_part}{gap}')
|
||||
# 대기 예약이 당일 유효로 바뀐 뒤로는 상한가 초과 발동가가 '오늘 도달 불가' 확정이다.
|
||||
# 거부하지는 않는다(관리자님 지시) — 무의미하다는 사실만 승인 전에 알린다.
|
||||
up_lim = market_data.get('upper_limit') or 0
|
||||
if up_lim and peak_v > up_lim:
|
||||
lines.append(f'{cfg["warning_emoji"]} 오늘 상한가 {_money(up_lim)} 초과 — '
|
||||
f'오늘은 도달 불가, 장 마감에 만료됩니다')
|
||||
else:
|
||||
lines.append(f'{marker} 시작 고점: {_md_bold(_money(peak_v))} (등록 시점){cur_part}')
|
||||
min_sell = request.get('min_sell_price')
|
||||
if min_sell:
|
||||
# 최저 매도가가 손절선을 결정했는지(=트레일보다 높았는지) 명시 — 둘 중 어느 게
|
||||
@@ -115,7 +131,10 @@ def format_card(request: dict, market_data: dict, card_id: str,
|
||||
who = f'{tied}개 계단 적용' if tied else '미적용 (트레일이 더 높음)'
|
||||
lines.append(f'{marker} 최저 매도가: {_md_bold(_money(min_sell))} ({who})')
|
||||
lines.append('')
|
||||
lines.append(f'{marker} 매도 계단 {len(steps)}단')
|
||||
steps_head = f'{marker} 매도 계단 {len(steps)}단'
|
||||
if is_arm:
|
||||
steps_head += ' (발동가 기준 예상)'
|
||||
lines.append(steps_head)
|
||||
for s in steps:
|
||||
cum = s.get('cum', 0)
|
||||
cum_part = '전량' if cum >= 100 else f'누적 {_num(cum)}%'
|
||||
@@ -127,6 +146,14 @@ def format_card(request: dict, market_data: dict, card_id: str,
|
||||
lines.append(f' ↳ 나머지 {rest:,}주는 스톱 없음 (계속 보유)')
|
||||
lines.append('')
|
||||
lines.append(f'{marker} 고점이 오르면 계단이 통째로 따라 올라갑니다 (내려가지 않음)')
|
||||
if is_arm:
|
||||
# 이 기능의 유일한 위험(발동 시 PIN 재확인 없음)을 승인 화면에서 고지하는 자리다.
|
||||
# 실제 손절선이 미리보기 이상인 이유: 발동 시점 현재가 ≥ 발동가.
|
||||
lines.append(f'{marker} 지금은 주문이 나가지 않습니다 — '
|
||||
f'발동가에 닿는 순간 PIN 재확인 없이 위 계단이 자동 발주됩니다')
|
||||
lines.append(f'{marker} {_md_bold("오늘 장 마감까지만 유효")} — '
|
||||
f'미발동이면 자동 취소됩니다 (스톱주문과 같은 당일 유효)')
|
||||
lines.append(f'{marker} 실제 손절선은 발동 시점 현재가 기준이라 위 값 이상입니다')
|
||||
elif request['order_type'] == 'MARKET':
|
||||
lines.append(f'{marker} 가격: {_md_bold("시장가")}')
|
||||
else:
|
||||
@@ -293,6 +320,45 @@ def format_trailing_submitted(info: dict) -> str:
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def format_arm_registered(info: dict) -> str:
|
||||
"""예약 트레일링 등록 완료 — 아직 키움에 주문이 없다는 사실을 분명히 적는다."""
|
||||
acct = _account_display(info.get('account', ''))
|
||||
head = '🧪 [모의] 예약 트레일링' if info.get('dry_run') else '⏳ 예약 트레일링'
|
||||
steps = info.get('steps') or []
|
||||
lines = [f'{head} 등록: {info["symbol_name"]} ({info.get("symbol", "")})',
|
||||
f'[{acct}] {info["qty"]:,}주 · 발동가 {_money(info["trigger_price"])} 도달 시 시작']
|
||||
if info.get('cur_price'):
|
||||
cur = info['cur_price']
|
||||
gap = (info['trigger_price'] - cur) / cur * 100
|
||||
lines.append(f'현재가 {_money(cur)} · 발동까지 {_pct(gap)}')
|
||||
if info.get('min_sell_price'):
|
||||
lines.append(f'최저 매도가 {_money(info["min_sell_price"])}')
|
||||
step_parts = []
|
||||
for s in steps:
|
||||
cum = s.get('cum', 0)
|
||||
cum_part = '전량' if cum >= 100 else f'누적 {_num(cum)}%'
|
||||
step_parts.append(f'−{_num(s["pct"])}% {cum_part}')
|
||||
lines.append(f'매도 계단 {len(steps)}단: ' + ' · '.join(step_parts))
|
||||
if info.get('arm_id'):
|
||||
lines.append(f'대기 시작 ({info["arm_id"]}) — **오늘 장 마감까지** 유효')
|
||||
lines.append('지금은 키움에 주문이 없습니다. 발동가에 닿으면 PIN 재확인 없이 자동 발주됩니다.')
|
||||
lines.append('미발동으로 장이 끝나면 자동 취소됩니다 (스톱주문과 같은 당일 유효).')
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def format_arm_fired(info: dict) -> str:
|
||||
"""예약 트레일링 발동 — 계단 접수 결과는 format_trailing_submitted 와 같은 모양이라 재사용하고,
|
||||
발동 경위(발동가·실제 발동가·수량 축소)만 앞뒤에 덧붙인다."""
|
||||
head = [f'🎯 예약 트레일링 발동 — {info["symbol_name"]} ({info.get("symbol", "")})',
|
||||
f'발동가 {_money(info["trigger_price"])} 도달 (현재가 {_money(info["peak"])})']
|
||||
if info.get('qty_reduced'):
|
||||
head.append(f'⚠️ 매도가능 {info["total_qty"]:,}주 — '
|
||||
f'예약 {info["arm_qty"]:,}주에서 줄여 재배분했습니다')
|
||||
body = format_trailing_submitted(info)
|
||||
tail = ['※ 스톱주문은 장 마감 시 소멸합니다 (자동 재등록 안 함)']
|
||||
return '\n'.join(head + ['', body] + tail)
|
||||
|
||||
|
||||
def format_expired(card_id: str, side: str) -> str:
|
||||
side_word = '매수' if side == 'BUY' else '매도'
|
||||
return f'⏱️ [#{card_id}] 승인 만료. {side_word}가 취소되었습니다.'
|
||||
|
||||
Reference in New Issue
Block a user