diff --git a/agents/budget/workspace/memory/2026-06-05.md b/agents/budget/workspace/memory/2026-06-05.md new file mode 100644 index 0000000..52e2b6d --- /dev/null +++ b/agents/budget/workspace/memory/2026-06-05.md @@ -0,0 +1,3 @@ +# 2026-06-05 + +- 하나은행 `문자통지수수료` 260원 건은 후잉 전송 실패가 아니라 구조화 파싱 실패 → raw 폴백 성공이었다. 원인: 하나은행 파서가 줄바꿈 형식만 받았고 실제 문자는 `[Web발신] 하나,06/05,13:08 ...` 한 줄 형식이었다. `parsers.py`의 `HANA_RE`를 공백/줄바꿈/쉼표 형식 모두 받도록 수정했고, `문자통지수수료`가 `기타비용 ← 하나은행(효원)`, item `잡비`로 overrides 적용되는 것까지 검증했다. diff --git a/agents/budget/workspace/skills/whooing-sync/scripts/parsers.py b/agents/budget/workspace/skills/whooing-sync/scripts/parsers.py index 1e7e4d9..e3182e1 100644 --- a/agents/budget/workspace/skills/whooing-sync/scripts/parsers.py +++ b/agents/budget/workspace/skills/whooing-sync/scripts/parsers.py @@ -33,12 +33,13 @@ def _normalize(text: str) -> str: HANA_RE = re.compile( - r"\[Web발신\]\s*\n" - r"하나,(?P\d{2}/\d{2})\s+(?P\d{2}:\d{2})\s*\n" - r"(?P\S+)\s*\n" - r"(?P입금|출금)(?P[\d,]+)원\s*\n?" - r"(?P[^\n]*)" - r"(?:\s*\n잔액(?P[\d,]+)원)?", + r"\[Web발신\]\s*" + r"하나,(?P\d{2}/\d{2})[,\s]+(?P\d{2}:\d{2})\s+" + r"(?P\S+)\s+" + r"(?P입금|출금)\s*(?P[\d,]+)원\s*" + r"(?P.*?)" + r"(?:\s+잔액\s*(?P[\d,]+)원?)?\s*$", + re.DOTALL, ) diff --git a/agents/stock/workspace/scripts/behive_web.py b/agents/stock/workspace/scripts/behive_web.py index b57188b..5cb4c51 100644 --- a/agents/stock/workspace/scripts/behive_web.py +++ b/agents/stock/workspace/scripts/behive_web.py @@ -4876,8 +4876,15 @@ table.market-adr td.adr-breakdown { font-size: 11px; color: #8b8f9a; } } /* 주문 모달 (order-modal) — 거래내역 보기용 trade-modal과 별개. 모달 박스 자체 스크롤 X (호가창만 스크롤) */ -.order-box { max-width: 560px; overflow-y: hidden; display: flex; flex-direction: column; } +.order-box { max-width: 560px; overflow-y: hidden; display: flex; flex-direction: column; transition: border-color 0.15s, box-shadow 0.15s, background 0.15s; } .order-box > [data-order-step="1"] { display: flex; flex-direction: column; flex: 1; min-height: 0; } +/* 매수/매도 모드 전체 색조 — 모달 어디를 봐도 side가 한눈에 (매수=빨강/매도=파랑) */ +.order-box.side-buy { border-color: #c9303e; box-shadow: 0 0 0 1px #c9303e, 0 12px 44px rgba(201,48,62,0.34); background: linear-gradient(180deg, rgba(201,48,62,0.12), rgba(201,48,62,0.02) 130px, #0d1018 300px); } +.order-box.side-sell { border-color: #2a5fb3; box-shadow: 0 0 0 1px #2a5fb3, 0 12px 44px rgba(42,95,179,0.34); background: linear-gradient(180deg, rgba(42,95,179,0.12), rgba(42,95,179,0.02) 130px, #0d1018 300px); } +.order-box.side-buy .modal-head { border-bottom: 2px solid rgba(201,48,62,0.55); padding-bottom: 10px; } +.order-box.side-sell .modal-head { border-bottom: 2px solid rgba(42,95,179,0.55); padding-bottom: 10px; } +.order-box.side-buy .orderbook { border-color: rgba(201,48,62,0.38); } +.order-box.side-sell .orderbook { border-color: rgba(42,95,179,0.38); } .order-symbol-select { background: #14171f; color: #f0f0f0; border: 1px solid #1f2330; border-radius: 6px; padding: 7px 9px; font-size: 14px; font-weight: 600; font-family: inherit; width: 100%; max-width: 360px; cursor: pointer; color-scheme: dark; } .order-symbol-select:focus { outline: none; border-color: #ff4d5e; } .order-symbol-select option { background: #14171f; color: #f0f0f0; } @@ -4926,8 +4933,9 @@ table.market-adr td.adr-breakdown { font-size: 11px; color: #8b8f9a; } .qty-step-buttons button[data-qty-step="max"] { background: #2a1f24; color: #ffcc77; } .qty-step-buttons button[data-qty-step="clear"] { background: #1d1418; color: #ff8a95; } .order-inputs input:disabled, .order-inputs select:disabled { opacity: 0.5; cursor: not-allowed; } -.order-inputs { display: flex; flex-direction: column; gap: 8px; min-height: 0; } -.order-inputs .order-actions { margin-top: auto; } +.order-inputs { display: flex; flex-direction: column; gap: 8px; min-height: 0; overflow-y: auto; } +/* 입력이 모달 높이를 넘쳐도 매수/취소 버튼은 항상 하단 고정 — 잘림 방지 */ +.order-inputs .order-actions { margin-top: auto; position: sticky; bottom: 0; background: #0d1018; padding-top: 8px; } .order-inputs label { display: flex; flex-direction: column; gap: 4px; font-size: 11px; color: #8b8f9a; } .order-inputs label.inline-row { flex-direction: row; align-items: center; gap: 8px; } .order-inputs label.inline-row > select { flex: 1; min-width: 0; } @@ -8100,7 +8108,7 @@ var ACCOUNTS = [ {label: '가희_일반', display: '가희 일반', owner: '가희'}, {label: '가희_ISA', display: '가희 ISA', owner: '가희'} ]; -var state = { code:'', name:'', side:'BUY', pollTimer:null, countdownTimer:null, expiryAt:0, isOpen:false, lastBook:null, bookCentered:false, lastCheck:null, marketActive:true, marketPhase:null, symbolsCache:null, pendingMaxOnSell:false, pendingMaxOnBuy:false }; +var state = { code:'', name:'', side:'BUY', pollTimer:null, countdownTimer:null, expiryAt:0, isOpen:false, lastBook:null, bookCentered:false, lastCheck:null, marketActive:true, marketPhase:null, symbolsCache:null, pendingMaxOnSell:false, pendingMaxOnBuy:false, accStatus:null }; function $(sel, root){ return (root||modal).querySelector(sel); } function $$(sel, root){ return (root||modal).querySelectorAll(sel); } function $p(sel){ return pinModal ? pinModal.querySelector(sel) : null; } @@ -8136,6 +8144,10 @@ function setMsg(text, kind){ function setSide(side){ state.side = side; $$('.order-side-toggle button[data-side]').forEach(function(b){ b.classList.toggle('active', b.getAttribute('data-side')===side); }); + // 모달 박스 전체에 side 색조 — 테두리·헤더 구분선·호가창 테두리가 매수=빨강/매도=파랑으로 물듦 + var obox = $('.order-box'); + if(obox){ obox.classList.toggle('side-buy', side==='BUY'); obox.classList.toggle('side-sell', side==='SELL'); } + decorateAccounts(); // 매수/매도에 따라 계좌 색·표기 전환 var sb = $('[data-order-submit]'); if(sb){ sb.setAttribute('data-side', side); sb.textContent = (side==='BUY'?'매수':'매도'); } renderQtyButtons(); // 매수일 때만 금액 input 표시 @@ -8234,6 +8246,7 @@ function renderBook(book){ } highlightSelectedTick(); updateMarketPhaseDisplay(); + decorateAccounts(); // 현재가 갱신 → 매수 예수금 충분 여부 색 갱신 if(!state.bookCentered){ var div = ob.querySelector('.ob-divider'); if(div){ @@ -8284,12 +8297,11 @@ function renderOrderInfo(){ var orderType = (($('[data-order-type]')||{}).value || 'LIMIT'); if(state.side === 'BUY'){ var lines = [ - '예수금 (D+2): '+fmt(d.d2_entra||0)+'원', - '매수가능액: '+fmt(d.ord_alow_amt||0)+'원' + '매수가능액 (D+2 예수금): '+fmt(d.d2_entra||0)+'원' ]; if(orderType === 'MARKET' && d.upper_limit){ var needed = (d.upper_limit||0) * qty; - var overCls = (needed > (d.ord_alow_amt||0)) ? ' class="qty-over"' : ''; + var overCls = (needed > (d.d2_entra||0)) ? ' class="qty-over"' : ''; lines.push('필요증거금 (상한가 '+fmt(d.upper_limit)+'원 기준): '+fmt(needed)+'원 ('+fmt(qty)+'주)'); } else { lines.push('주문예상액: '+fmt(amount)+'원 ('+fmt(qty)+'주)'); @@ -8487,12 +8499,16 @@ function populateSymbolSelect(currentCode, currentName){ var groups = {'보유':[], '관심':[], '감시':[], '현재':[]}; opts.concat(items).forEach(function(it){ (groups[it.source]||groups['관심']).push(it); }); var html = ''; + // 종목코드 단위 통합 — 같은 종목이 여러 계좌(본인·가희)에 있어도 하나만. 매매 계좌는 별도 select. + // 라벨은 종목명만 (owner·종목코드 제거). 중복 시 우선순위 그룹(현재>보유>관심>감시)만 노출. + var seen = {}; ['현재','보유','관심','감시'].forEach(function(g){ if(!groups[g] || !groups[g].length) return; + var rows = groups[g].filter(function(it){ if(seen[it.code]) return false; seen[it.code]=1; return true; }); + if(!rows.length) return; html += ''; - groups[g].forEach(function(it){ - var ownerTag = it.owner ? ' ['+it.owner+']' : ''; - var label = (it.name||it.code) + ownerTag + ' ('+it.code+')'; + rows.forEach(function(it){ + var label = (it.name||it.code); var selAttr = (it.code === currentCode) ? ' selected' : ''; html += ''; }); @@ -8532,6 +8548,9 @@ function onSymbolChange(){ // 종목 변경 시 새 max로 자동 채움 (SELL: 보유 100%, BUY: 매수가능 max) if(state.side === 'SELL') state.pendingMaxOnSell = true; else state.pendingMaxOnBuy = true; + state.accStatus = null; + decorateAccounts(); + fetchAccountStatus(); pollOnce(); updateCheck(); } @@ -8542,6 +8561,38 @@ function fillAccounts(allowed){ if(allowed && allowed.length){ list = ACCOUNTS.filter(function(a){ return allowed.indexOf(a.label) >= 0; }); } list.forEach(function(a){ var opt = document.createElement('option'); opt.value=a.label; opt.textContent=a.display; sel.appendChild(opt); }); } +// 계좌 옵션에 색 + 텍스트 병행 표시. 매수: 예수금<현재가(1주)면 붉은색·'⚠ 부족'. 매도: 매도가능 주수 표기, 0이면 붉은색. +// iOS Safari는 option 글자색 무시 → 텍스트(주수·부족)가 폰 fallback. +var ACC_DISPLAY = {}; ACCOUNTS.forEach(function(a){ ACC_DISPLAY[a.label] = a.display; }); +function decorateAccounts(){ + var sel = $('[data-order-account]'); if(!sel) return; + var st = state.accStatus; + var price = (state.lastBook && state.lastBook.price) || (st && st.cur_price) || 0; + Array.prototype.forEach.call(sel.options, function(opt){ + var base = ACC_DISPLAY[opt.value] || opt.value; + if(!st || !st.byLabel || !st.byLabel[opt.value]){ opt.textContent = base; opt.style.color=''; return; } + var a = st.byLabel[opt.value]; + if(state.side === 'SELL'){ + var q = a.trde_able_qty || 0; // 매도가능 수량 (미체결 매도로 묶인 분 제외) + opt.textContent = base + (q > 0 ? ' · ' + fmt(q) + '주' : ' · 미보유'); + opt.style.color = q > 0 ? '#f0f0f0' : '#ff6b75'; + } else { + var ok = price > 0 ? (a.d2_entra >= price) : (a.d2_entra > 0); + opt.textContent = base + (ok ? '' : ' · ⚠ 부족'); + opt.style.color = ok ? '#f0f0f0' : '#ff6b75'; + } + }); +} +function fetchAccountStatus(){ + if(!state.code){ state.accStatus = null; decorateAccounts(); return; } + var code = state.code; + fetch('/api/order/account_status?code=' + encodeURIComponent(code)).then(function(r){ return r.json(); }).then(function(d){ + if(state.code !== code) return; // 그새 종목 바뀌면 무시 + var byLabel = {}; (d.accounts||[]).forEach(function(a){ byLabel[a.label] = a; }); + state.accStatus = { byLabel: byLabel, cur_price: d.cur_price || 0 }; + decorateAccounts(); + }).catch(function(){}); +} function reset(){ setMsg(''); $('[data-order-type]').value = 'LIMIT'; @@ -8593,6 +8644,8 @@ function openModal(opts){ fillAccounts(opts.accounts); if(opts.account){ var sel = $('[data-order-account]'); if(sel) sel.value = opts.account; } populateSymbolSelect(state.code, state.name); + state.accStatus = null; + fetchAccountStatus(); setSide(opts.side === 'SELL' ? 'SELL' : 'BUY'); if(opts.price){ $('[data-order-price-input]').value = opts.price; } modal.classList.remove('hidden'); @@ -8676,7 +8729,7 @@ function doPropose(){ var basis = (orderType === 'MARKET' && lc.upper_limit) ? '상한가' : '단가'; var ref = (orderType === 'MARKET' && lc.upper_limit) ? lc.upper_limit : price; var needed = ref * qty; - setMsg('예수금 부족 — 필요 ' + needed.toLocaleString() + '원 (' + basis + ' ' + (ref||0).toLocaleString() + '원 × ' + qty + '주) / 가용 ' + (lc.ord_alow_amt||0).toLocaleString() + '원\n최대 ' + lc.max_qty + '주까지 매수 가능', 'error'); + setMsg('예수금 부족 — 필요 ' + needed.toLocaleString() + '원 (' + basis + ' ' + (ref||0).toLocaleString() + '원 × ' + qty + '주) / 가용 ' + (lc.d2_entra||0).toLocaleString() + '원\n최대 ' + lc.max_qty + '주까지 매수 가능', 'error'); } else { setMsg('보유 부족 — 매도 ' + qty + '주 / 매도가능 ' + lc.max_qty + '주', 'error'); } @@ -8826,7 +8879,8 @@ document.addEventListener('click', function(e){ if(subOrderBtn){ function openWithFirst(items){ var first = (items || []).find(function(it){ return it.source === '보유' && it.owner === '본인'; }); - if(first){ openModal({code: first.code, name: first.name}); } + // 보유종목을 자동선택하므로 행 거래버튼과 동일 규칙으로 매도탭 진입. 보유분 없으면 빈 모달(매수 기본). + if(first){ openModal({code: first.code, name: first.name, side: 'SELL'}); } else { openModal({code: '', name: ''}); } } if(state.symbolsCache){ openWithFirst(state.symbolsCache); } @@ -9405,9 +9459,12 @@ class Handler(BaseHTTPRequestHandler): 'order_type': order_type, 'price': price} if side == 'BUY': bal = kc.get_balance(account) - ord_alow = int(bal.get('ord_alow_amt') or 0) - result['ord_alow_amt'] = ord_alow - result['d2_entra'] = int(bal.get('d2_entra') or 0) + # 매수가능 기준은 d2_entra(D+2 정산예수금) — propose 가드(validate_balance_for_buy)와 + # 동일. kt00001 ord_alow_amt는 오늘 즉시현금(entr)만 잡혀 매도결제분(D+2)을 빠뜨려 + # max_qty가 과소 계산되고 "예수금 부족" 오탐을 냈다. ord_alow_amt는 참고 표시용만 유지. + d2 = int(bal.get('d2_entra') or 0) + result['ord_alow_amt'] = int(bal.get('ord_alow_amt') or 0) + result['d2_entra'] = d2 if order_type == 'MARKET': # 키움 시장가 매수 증거금 = 상한가 × qty. 같은 기준으로 max_qty 표시. try: @@ -9417,10 +9474,9 @@ class Handler(BaseHTTPRequestHandler): except Exception: upper = 0 result['upper_limit'] = upper - result['max_qty'] = (ord_alow // upper) if upper > 0 else 0 + result['max_qty'] = (d2 // upper) if upper > 0 else 0 else: - # 키움 ord_alow_amt는 수수료 버퍼 이미 반영된 매수가능액. price>0 일 때만 max_qty. - result['max_qty'] = (ord_alow // price) if price > 0 else 0 + result['max_qty'] = (d2 // price) if price > 0 else 0 else: # SELL positions = kc.get_positions(account) pos = next((p for p in positions if p['code'] == code), None) @@ -9442,6 +9498,38 @@ class Handler(BaseHTTPRequestHandler): return self._send_json(200, result) return + if parsed.path == '/api/order/account_status': + # 계좌 select 색/보유주수 표시용 — 한 종목에 대해 4계좌 d2_entra·보유주수 일괄 반환. + qs = parse_qs(parsed.query) + code = ''.join(ch for ch in (qs.get('code') or [''])[0].strip() if ch.isalnum()) + if not code: + self._send_json(400, {'error': 'code required'}) + return + try: + import kiwoom_client as kc + labels = [a['label'] for a in kc.list_accounts()] + with ThreadPoolExecutor(max_workers=len(labels) + 2) as ex: + pos_f = ex.submit(kc.get_positions_all, exchange='NXT', labels=labels) + bal_fs = {lb: ex.submit(kc.get_balance, lb) for lb in labels} + pos_by = pos_f.result() + bals = {lb: bal_fs[lb].result() for lb in labels} + cur_price = 0 + accounts = [] + for lb in labels: + p = next((x for x in (pos_by.get(lb, []) or []) if x['code'] == code), None) + if p and p.get('cur_price') and not cur_price: + cur_price = p['cur_price'] + accounts.append({ + 'label': lb, + 'd2_entra': int(bals[lb].get('d2_entra') or 0), + 'hold_qty': p['qty'] if p else 0, + 'trde_able_qty': p['trde_able_qty'] if p else 0, + }) + self._send_json(200, {'code': code, 'cur_price': cur_price, 'accounts': accounts}) + except Exception as e: + traceback.print_exc() + self._send_json(500, {'error': f'account_status failed: {e}'}) + return if parsed.path == '/api/chart_svg': qs = parse_qs(parsed.query) code = ''.join(ch for ch in (qs.get('code') or [''])[0].strip() if ch.isalnum()) diff --git a/agents/stock/workspace/state/market_indicators_history.jsonl b/agents/stock/workspace/state/market_indicators_history.jsonl index ee799ac..267681a 100644 --- a/agents/stock/workspace/state/market_indicators_history.jsonl +++ b/agents/stock/workspace/state/market_indicators_history.jsonl @@ -18,3 +18,5 @@ {"date": "2026-06-02", "market": "KOSPI", "market_label": "코스피", "rise": 271, "upper": 1, "fall": 636, "lower": 0, "steady": 17, "adr": 42.77, "personal": 63537, "foreign": -63035, "institutional": -546, "captured_at": "2026-06-02T21:00:00.850650+09:00"} {"date": "2026-06-04", "market": "KOSDAQ", "market_label": "코스닥", "rise": 834, "upper": 8, "fall": 849, "lower": 1, "steady": 57, "adr": 99.06, "personal": -1633, "foreign": -424, "institutional": 2066, "captured_at": "2026-06-04T21:00:02.407555+09:00"} {"date": "2026-06-04", "market": "KOSPI", "market_label": "코스피", "rise": 447, "upper": 2, "fall": 446, "lower": 0, "steady": 31, "adr": 100.67, "personal": 50135, "foreign": -66663, "institutional": 15255, "captured_at": "2026-06-04T21:00:02.407555+09:00"} +{"date": "2026-06-05", "market": "KOSDAQ", "market_label": "코스닥", "rise": 389, "upper": 4, "fall": 1298, "lower": 0, "steady": 49, "adr": 30.28, "personal": 337, "foreign": -1878, "institutional": 1508, "captured_at": "2026-06-05T21:00:05.007446+09:00"} +{"date": "2026-06-05", "market": "KOSPI", "market_label": "코스피", "rise": 225, "upper": 1, "fall": 672, "lower": 0, "steady": 26, "adr": 33.63, "personal": 42240, "foreign": -27638, "institutional": -13809, "captured_at": "2026-06-05T21:00:05.007446+09:00"} diff --git a/agents/stock/workspace/state/trade_journal.jsonl b/agents/stock/workspace/state/trade_journal.jsonl index 2abfca2..0773348 100644 --- a/agents/stock/workspace/state/trade_journal.jsonl +++ b/agents/stock/workspace/state/trade_journal.jsonl @@ -58,3 +58,15 @@ {"date": "2026-05-28", "account": "가희_일반", "owner": "가희", "code": "005930", "name": "삼성전자", "buy_qty": 2, "buy_avg": 299500, "buy_amt": 599000, "sell_qty": 0, "sell_avg": 0, "sell_amt": 0, "pl_amt": 0, "cmsn_tax": 80, "prft_rt": 0.0, "collected_at": "2026-05-28T21:34:52.517424+09:00"} {"date": "2026-05-28", "account": "가희_ISA", "owner": "가희", "code": "064400", "name": "LG씨엔에스", "buy_qty": 7, "buy_avg": 89429, "buy_amt": 626000, "sell_qty": 0, "sell_avg": 0, "sell_amt": 0, "pl_amt": 0, "cmsn_tax": 90, "prft_rt": 0.0, "collected_at": "2026-05-28T21:34:52.517424+09:00"} {"date": "2026-05-29", "account": "일반", "owner": "본인", "code": "477850", "name": "마키나락스", "buy_qty": 0, "buy_avg": 0, "buy_amt": 0, "sell_qty": 1, "sell_avg": 34550, "sell_amt": 34550, "pl_amt": -3969, "cmsn_tax": 69, "prft_rt": -10.32, "collected_at": "2026-05-29T23:57:18.862755+09:00"} +{"date": "2026-06-05", "account": "가희_일반", "owner": "가희", "code": "005930", "name": "삼성전자", "buy_qty": 1, "buy_avg": 326000, "buy_amt": 326000, "sell_qty": 0, "sell_avg": 0, "sell_amt": 0, "pl_amt": 0, "cmsn_tax": 40, "prft_rt": 0.0, "collected_at": "2026-06-05T21:06:30.016666+09:00"} +{"date": "2026-06-05", "account": "가희_일반", "owner": "가희", "code": "066570", "name": "LG전자", "buy_qty": 0, "buy_avg": 0, "buy_amt": 0, "sell_qty": 1, "sell_avg": 311500, "sell_amt": 311500, "pl_amt": 73338, "cmsn_tax": 662, "prft_rt": 30.88, "collected_at": "2026-06-05T21:06:30.016666+09:00"} +{"date": "2026-06-05", "account": "가희_ISA", "owner": "가희", "code": "005380", "name": "현대차", "buy_qty": 0, "buy_avg": 0, "buy_amt": 0, "sell_qty": 3, "sell_avg": 653000, "sell_amt": 1959000, "pl_amt": 421292, "cmsn_tax": 4207, "prft_rt": 27.47, "collected_at": "2026-06-05T21:06:30.016666+09:00"} +{"date": "2026-06-05", "account": "가희_ISA", "owner": "가희", "code": "017670", "name": "SK텔레콤", "buy_qty": 0, "buy_avg": 0, "buy_amt": 0, "sell_qty": 16, "sell_avg": 112400, "sell_amt": 1798400, "pl_amt": 191536, "cmsn_tax": 3856, "prft_rt": 11.95, "collected_at": "2026-06-05T21:06:30.016666+09:00"} +{"date": "2026-06-05", "account": "가희_ISA", "owner": "가희", "code": "064400", "name": "LG씨엔에스", "buy_qty": 0, "buy_avg": 0, "buy_amt": 0, "sell_qty": 7, "sell_avg": 117000, "sell_amt": 819000, "pl_amt": 191240, "cmsn_tax": 1757, "prft_rt": 30.55, "collected_at": "2026-06-05T21:06:30.016666+09:00"} +{"date": "2026-06-05", "account": "가희_ISA", "owner": "가희", "code": "066570", "name": "LG전자", "buy_qty": 0, "buy_avg": 0, "buy_amt": 0, "sell_qty": 2, "sell_avg": 321000, "sell_amt": 642000, "pl_amt": 188126, "cmsn_tax": 1374, "prft_rt": 41.57, "collected_at": "2026-06-05T21:06:30.016666+09:00"} +{"date": "2026-06-05", "account": "일반", "owner": "본인", "code": "003550", "name": "LG", "buy_qty": 0, "buy_avg": 0, "buy_amt": 0, "sell_qty": 20, "sell_avg": 122100, "sell_amt": 2442000, "pl_amt": 229756, "cmsn_tax": 5244, "prft_rt": 10.41, "collected_at": "2026-06-05T21:06:49.660822+09:00"} +{"date": "2026-06-05", "account": "ISA", "owner": "본인", "code": "005380", "name": "현대차", "buy_qty": 0, "buy_avg": 0, "buy_amt": 0, "sell_qty": 1, "sell_avg": 655000, "sell_amt": 655000, "pl_amt": 18601, "cmsn_tax": 1399, "prft_rt": 2.93, "collected_at": "2026-06-05T21:06:49.660822+09:00"} +{"date": "2026-06-05", "account": "ISA", "owner": "본인", "code": "005930", "name": "삼성전자", "buy_qty": 2, "buy_avg": 326000, "buy_amt": 652000, "sell_qty": 0, "sell_avg": 0, "sell_amt": 0, "pl_amt": 0, "cmsn_tax": 90, "prft_rt": 0.0, "collected_at": "2026-06-05T21:06:49.660822+09:00"} +{"date": "2026-06-05", "account": "ISA", "owner": "본인", "code": "017670", "name": "SK텔레콤", "buy_qty": 0, "buy_avg": 0, "buy_amt": 0, "sell_qty": 11, "sell_avg": 108300, "sell_amt": 1191300, "pl_amt": 102345, "cmsn_tax": 2551, "prft_rt": 9.42, "collected_at": "2026-06-05T21:06:49.660822+09:00"} +{"date": "2026-06-05", "account": "ISA", "owner": "본인", "code": "0177A0", "name": "WON 두산그룹포커스", "buy_qty": 0, "buy_avg": 0, "buy_amt": 0, "sell_qty": 44, "sell_avg": 12111, "sell_amt": 532890, "pl_amt": 31220, "cmsn_tax": 70, "prft_rt": 6.22, "collected_at": "2026-06-05T21:06:49.660822+09:00"} +{"date": "2026-06-05", "account": "ISA", "owner": "본인", "code": "233740", "name": "KODEX 코스닥150레버리지", "buy_qty": 116, "buy_avg": 12880, "buy_amt": 1494080, "sell_qty": 0, "sell_avg": 0, "sell_amt": 0, "pl_amt": 0, "cmsn_tax": 220, "prft_rt": 0.0, "collected_at": "2026-06-05T21:06:49.660822+09:00"}