auto: 일일 백업 2026-08-26 02:00

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hyowons
2026-08-26 02:00:02 +09:00
parent 600be2ee96
commit 992b85005d
162 changed files with 3704 additions and 1825 deletions
+5 -3
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -18,7 +18,7 @@
| **증권잔액 → 골디 inbox** | `python3 ~/.openclaw/agents/stock/workspace/scripts/send_balance_to_budget.py` | launchd `ai.openclaw.stock.send-balance` (매월 1일 04:30) |
| **골디 inbox 처리** (단독) | `python3 ~/.openclaw/agents/budget/workspace/skills/monthly-settlement/scripts/inbox_handler.py [--dry-run]` | 트리거 없음 — 월간결산 cron 진입부에서 자동 호출. 수동 점검·재처리용 |
| **워치리스트 모니터링** | `python3 ~/.openclaw/agents/stock/workspace/scripts/watchlist_monitor.py check` | launchd `ai.openclaw.stock.watchlist-monitor` (평일 09–15시 매 15분) |
| **트레일링 스톱 감시** | `python3 ~/.openclaw/agents/stock/workspace/scripts/trailing_monitor.py {check [--repeat N --gap S] [--force] [--dry-run]\|list}` | launchd `ai.openclaw.stock.trailing-monitor` (평일 09:00–15:30 **매 1분**, 스크립트가 장외·휴장 self-skip). 자산웹에서 PIN 승인된 트레일링 예약의 조건단가를 고점 갱신 시 kt10002로 **상향만** 정정. 신규 발주 없음. 예약 0건이면 즉시 종료(API 콜 0). 상태 `state/trailing_stops.json`. ⚠️ cadence 변경 이력 2분→30초→**1분**(2026-07-30 관리자님) — 30초는 launchd로 불가해 `--repeat 2 --gap 30`(프로세스 내부 2회)으로 구현했었고, 옵션은 살아있으니 다시 쓸 땐 plist 인자만 추가 |
| **트레일링 스톱 감시** | `python3 ~/.openclaw/agents/stock/workspace/scripts/trailing_monitor.py {check [--repeat N --gap S] [--force] [--dry-run]\|list}` | launchd `ai.openclaw.stock.trailing-monitor` (평일 **08:00–19:59 매 1분**, 720엔트리 — 2026-08-25 plist 실측 정정. 스크립트가 장외·휴장 self-skip). 하는 일 둘: ①자산웹에서 PIN 승인된 트레일링 예약의 조건단가를 고점 갱신 시 kt10002로 **상향만** 정정 ②**예약 트레일링(TRAILING_ARM) 발동·만료** — 발동가 도달 시 스톱주문 신규 발주 + 예약 승격, 미발동으로 등록일이 지난 대기 예약은 자동 취소(둘 다 2026-08-25 추가, 관리자님 승인). ⚠️ "신규 발주 없음"은 더 이상 사실이 아니다. 예약 0건이면 즉시 종료(API 콜 0). 상태 `state/trailing_stops.json`. ⚠️ cadence 변경 이력 2분→30초→**1분**(2026-07-30 관리자님) — 30초는 launchd로 불가해 `--repeat 2 --gap 30`(프로세스 내부 2회)으로 구현했었고, 옵션은 살아있으니 다시 쓸 땐 plist 인자만 추가 |
| **공모주 캘린더 동기화** | `python3 ~/.openclaw/agents/stock/workspace/scripts/ipo_calendar_sync.py` | launchd `ai.openclaw.stock.ipo-calendar-sync` (매월 1일 09:00) |
| **FOMC 캘린더 동기화** | `python3 ~/.openclaw/agents/stock/workspace/scripts/fomc_calendar_sync.py [--list\|--dry-run]` | launchd `ai.openclaw.stock.fomc-calendar-sync` (매월 1일 09:10). 연준 공식 일정표 → `[FOMC] YYYY-MM 회의` 종일 일정(회의 시작일 ET ~ 발표일 KST). `--list`는 파싱 결과만(캘린더 미접근) |
| **휴장일 동기화** | `python3 ~/.openclaw/agents/stock/workspace/scripts/holiday_sync.py [--show]` | launchd `ai.openclaw.stock.holiday-sync` (매주 일요일 03:00) |
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -33,13 +33,16 @@ Be the assistant you'd actually want to talk to. Concise when needed, thorough w
레이는 **매매를 결정하지 않는다.** 결정권은 항상 관리자님 PIN echo 에 있다.
- **LLM 자율 매매 금지** — 워치리스트 도달·시장 이벤트·뉴스·자체 추론으로 매수/매도 자동 트리거 X. 매매는 오직 관리자님이 텔레그램에 직접 명시적으로 지시할 때만.
- **LLM 자율 매매 금지** — 워치리스트 도달·시장 이벤트·뉴스·자체 추론으로 매수/매도 자동 트리거 X. 매매는 오직 관리자님이 텔레그램에 직접 명시적으로 지시할 때만. (가격 도달로 주문이 나가는 경로가 하나 있다 — 자산웹에서 PIN 으로 사전 승인한 **예약 트레일링**뿐이고, 아래 「자동 트리거 등록 금지」의 예외 항목을 볼 것. 레이가 만들 수 있는 경로는 아니다.)
- **레이의 역할 = 페이로드 추출** — 자연어("삼성 5주 75,000 원에 사줘") → 종목코드·계좌·수량·가격·주문방식을 dict 로 정리해서 `orders.handler.propose_trade` 진입점에 전달. 그 외엔 일체 매매 도구를 부르지 않는다.
- **카드+PIN 분리 발송** — propose_trade 가 카드 메시지(매수/매도·계좌·종목·가격 4개 하이라이트) + PIN 메시지(단독 4자리 숫자, 가희 계좌면 8자리 영숫자) 두 개를 텔레그램에 보낸다.
- **PIN echo 가 마지막 게이트** — 관리자님이 PIN 을 그대로 답장한 순간만 실주문이 나간다. 레이 LLM 이 PIN 을 자기 판단으로 만들거나 변형하면 절대 안 된다.
- **사이드카 ON 상태에선 모든 매매 거부** — `state/orders_disabled` 파일 존재 시 진입점 첫 줄에서 차단. `/orders_off` `/orders_on` 으로 토글.
- **조회 vs 주문 분리 유지** — `kiwoom_client.py` 는 여전히 조회 전용. 주문 함수는 `orders/` 패키지로 분리. 조회 모듈에 주문 함수 추가 금지.
- **매매 관련 자동 트리거 등록 금지** — 매매·매매재시도·취소·재발행 등 `orders/` 모듈 진입점을 cron `jobs.json` 또는 launchd plist 에 자동 발화 항목으로 등록하지 않는다. 실패·거절·쿨다운된 매매는 관리자님이 텔레그램으로 다시 명시적으로 지시할 때까지 그대로 둔다. "쿨다운 끝나면 다시 시도하자"는 충동도 X — 자동 재시도 등록 자체가 LLM 자율 매매와 동급. 자동화가 필요해 보이면 직접 등록하지 말고 관리자님께 설계 안건으로 제안.
- **승인된 예외는 `ai.openclaw.stock.trailing-monitor.plist` 하나뿐**이고, 그 안에서 허용된 매매 API 는 ①`modify_order`(트레일링 손절선 **상향** 정정, 2026-07-30 승인) ②**예약 트레일링(TRAILING_ARM)의 발동가 도달 시 스톱주문 발주**(2026-08-25 승인) 2종이다.
- 허용 근거는 "발주가 없다"가 **아니라** "계좌·종목·수량·계단·발동가가 **PIN 승인 시점에 전부 확정**되고, 감시는 확정된 내용을 조건 도달 시 집행할 뿐 LLM 판단이 개입하지 않는다" 다. 판단 기준을 이렇게 읽어야 한다 — **누가 매매의 *내용*(계좌·종목·수량·방향·가격)을 정하는가.** 사람이 PIN 으로 정했으면 집행 자동화는 별건 승인 대상, 감시 루프나 LLM 이 정하면 금지.
- 이 예외를 근거로 **새 자동 트리거를 스스로 늘리지 않는다.** 감시가 수량을 늘리거나 방향을 바꾸는 경로도 여전히 없다(매도가능 부족 시 **축소**만).
- **자동매매·알고리즘 매매 요청** — 이 원칙을 환기하고 사이드카·1주 검증·가드 보강을 거친 별도 설계 안건으로 미룬다.
상세 사양과 호출 진입점은 `orders/__init__.py`, 한도값은 `orders/limits.json` 참조. 사용은 `skills/order-trading`, `skills/order-controls`.
+363 -61
View File
@@ -808,6 +808,26 @@ def _market_phase_state() -> dict:
return {'phase': 'closed', 'label': '장외 시간', 'active': False, 'time': time_str}
# phase 경계 시각(분) — _market_phase_state 의 분기 경계와 같은 값.
_PHASE_BOUNDARIES_MIN = (8 * 60, 9 * 60, 15 * 60 + 30, 20 * 60)
def _secs_to_next_phase() -> int | None:
"""오늘 남은 다음 phase 경계까지의 초. 주말·휴장일·20:00 이후엔 None
(브라우저는 기본 60초 주기로 폴링).
⚠️ now.second 절사 덕에 값이 항상 실제보다 크거나 같다 → 이 값으로 잡은 타이머가
경계보다 일찍 깨는 일이 없다(일찍 깨면 옛 phase 를 읽고 다음 경계까지 60초 주기로 밀린다)."""
now = datetime.now(KST)
if now.weekday() >= 5 or now.strftime('%Y-%m-%d') in _load_holidays():
return None
cur = now.hour * 3600 + now.minute * 60 + now.second
for b in _PHASE_BOUNDARIES_MIN:
if b * 60 > cur:
return b * 60 - cur
return None
def _bump_panel_signal() -> None:
"""주문 접수/취소 등 이벤트 발생 시 state/fill_signal 갱신 → 브라우저 1초 틱(realtime)이
fill_seq 변화를 감지해 패널을 1회 갱신. 3초 주기 전면 swap 폐지(2026-07-22, 이벤트 기반 전환)
@@ -3954,6 +3974,45 @@ _TRAILING_LOOKUP_TTL = 5.0
_trailing_lookup_cache: dict = {'ts': 0.0, 'by_ord': {}}
def _arm_rows() -> list:
"""대기 예약 → 미체결 목록에 섞을 합성 행. 상태파일 읽기라 키움 콜 0.
JS 렌더러가 읽는 키를 그대로 채우고 `kind='arm'` 으로 표시한다. 취소 경로를 물리적으로
분리하려고 `ord_no` 는 비워 둔다 — 미체결 취소(kt10003)가 이 행을 삼키면 안 된다.
현재가는 실시간 허브에서만 읽는다(없으면 0) — 팝업 열 때 키움 조회가 나가면 안 된다.
"""
out = []
try:
from orders import trailing as _trl
arms = _trl.list_arms()
except Exception:
return out
hub = _RT_HUB
for a in arms:
cur = 0
if hub is not None:
q = hub.get_quote(a['symbol']) or {}
cur = q.get('price') or 0
out.append({
'account': a['account'], 'ord_no': '', 'code': a['symbol'], 'name': a['symbol_name'],
'side': 'SELL', 'order_qty': a['qty'], 'unfilled_qty': a['qty'],
'order_price': 0, 'order_type': '예약 트레일링', 'stop_price': 0,
'cur_price': cur, 'exchange': '-', 'exchange_code': '',
'status': '발동 대기', 'order_time': (a.get('created_at') or '')[5:16].replace('T', ' '),
'trailing': None,
'kind': 'arm',
'arm': {'id': a['id'], 'trigger_price': a['trigger_price'],
'min_sell_price': a.get('min_sell_price'),
'steps': a.get('steps') or [], 'firing_at': a.get('firing_at'),
'fired_legs': a.get('fired_legs') or [],
'created_at': a.get('created_at'),
# 감시 루프는 08:00~19:59 에만 돌아 20:00 직후엔 아직 파일에 남아 있다.
# 숨기지 않고 '만료' 로 표시한다 — 화면이 실제 상태와 달라선 안 된다.
'expired': _trl.is_arm_expired(a)},
})
return out
def _trailing_info_for(ord_no: str):
"""미체결 주문번호 → 트레일링 예약 요약 (없으면 None).
@@ -8495,6 +8554,7 @@ def _render_order_modal() -> str:
'<option value="MARKET">시장가</option>'
'<option value="STOP_LIMIT">스톱지정가 (하락 시 매도)</option>'
'<option value="TRAILING_STOP">트레일링 스톱 (고점 따라 손절선 상승)</option>'
'<option value="TRAILING_ARM">예약 트레일링 (발동가 도달 시 시작)</option>'
'</select></label>'
'<label data-order-stop-row style="display:none;">'
'<span>조건단가 <span class="muted small" style="font-weight:400;">(이 가격 도달 시 매도)</span></span>'
@@ -8504,6 +8564,14 @@ def _render_order_modal() -> str:
# 미리보기는 클라이언트에서 같은 식으로 계산해 보여주되 실제 값은 서버 계산분이 승자.
# 고점 기준 select 제거(2026-07-30) — 과거 고점(52주·매수후) 기준은 손절선이 현재가
# 위로 올라가 등록 즉시 발동하는 구조라 쓸 수 없었다. 초기 고점은 등록 시점 현재가 고정.
# 예약 트레일링: 발동가 하나만 더 받는다. 계단·최저 매도가 입력은 트레일링과 공유하고,
# 미리보기의 기준(peak)만 현재가 → 발동가로 바뀐다. 트레일 행 **위**에 두는 이유는
# 읽는 순서가 "언제 시작 → 어떻게 판다" 여야 해서다.
'<label data-order-arm-row style="display:none;">'
'<span>발동가 <b style="color:#d9a84a;">(원)</b> '
'<span class="muted small" style="font-weight:400;">이 가격까지 오르면 트레일링 시작</span></span>'
'<input type="number" inputmode="numeric" min="0" step="1" data-order-arm-price>'
'</label>'
'<label data-order-trail-row style="display:none;">'
# 단위(%·원)를 라벨에 박는다 — 입력칸 옆에 붙이면 가로로 벌어져 스크롤이 생기고,
# number input 안쪽 absolute 접미사는 데스크톱 스피너와 겹친다.
@@ -8842,6 +8910,8 @@ def render_html() -> str:
valid_tids = [tid for tid, _ in descs]
first_tid = descs[0][0]
market_active = _market_active_now()
market_phase = _market_phase_state()['phase']
market_next_in = _secs_to_next_phase()
now = datetime.now(KST).strftime('%Y-%m-%d %H:%M:%S')
# 계단 프리셋은 셸에 실어 보낸다 — 거래 모달을 열 때마다 fetch 하면 느리고,
# 고정 4종이라 셸에 넣어도 부담이 없다.
@@ -10127,32 +10197,42 @@ def render_html() -> str:
# 여기 스크립트는 상단 상태 점(🟢장중/⚪장마감)만 유지 + 개장/마감 전환 시 1회 재렌더
# (day-change 게이트·시간대 표시 반영). 정상 상태에선 주기적 full swap 없음.
market_active_js = 'true' if market_active else 'false'
market_next_in_js = 'null' if market_next_in is None else str(market_next_in)
auto_reload_script = (
'<script>(function(){'
f'var MARKET={market_active_js};'
'var SLOW_MS=60000,poll=null,cur=MARKET;'
f'var MARKET={market_active_js},PHASE="{market_phase}",NEXT_IN={market_next_in_js};'
'var SLOW_MS=60000,poll=null,cur=MARKET,curPhase=PHASE;'
'var HSEQ_MS=20000,hpoll=null,hseq=null;' # 외부 매매 감지 폴링(장중만)
'var badge=document.getElementById("auto-badge");'
# 배지 점: 장중 초록(.live) / 장마감 어두운 회색. 공유 플래그(apply·폴링이 함께 갱신) 우선, 없으면 cur.
'function dot(){var m=(typeof window.__behive_market_active!=="undefined")?!!window.__behive_market_active:cur;if(badge)badge.classList.toggle("live",m);}'
'function stopPoll(){if(poll){clearInterval(poll);poll=null;}}'
# 60초마다 /api/market_state(키움 미경유·시각+휴장일만) 확인 → 배지 갱신 + 개장/마감 전환 시에만 1회 재렌더.
'function startPoll(){'
'stopPoll();'
'poll=setInterval(function(){'
'if(document.visibilityState!=="visible")return;'
'fetch("/api/market_state",{cache:"no-store",credentials:"same-origin"})'
'.then(function(r){return r.json();})'
'.then(function(s){'
'var act=!!(s&&s.active);'
'if(act===cur)return;'
'cur=act;'
'function stopPoll(){if(poll){clearTimeout(poll);poll=null;}}'
'function schedule(ms){stopPoll();poll=setTimeout(tick,ms);}'
# 다음 phase 경계(09:00·15:30·08:00·20:00)가 기본 주기보다 가까우면 그 직후(+2s)로 당겨 잡는다.
# setInterval 고정 60초로는 경계와 최대 60초 어긋나 초록점·OHLC 강조가 늦게 바뀐다.
'function nextWait(n){'
'if(n===null||n===undefined||n*1000>SLOW_MS)return SLOW_MS;'
'return Math.max(1000,(n+2)*1000);'
'}'
# /api/market_state(키움 미경유·시각+휴장일만) 확인 → 배지 갱신 + phase 전환 시 1회 재렌더.
'function tick(){'
'if(document.visibilityState!=="visible"){schedule(SLOW_MS);return;}'
'fetch("/api/market_state",{cache:"no-store",credentials:"same-origin"})'
'.then(function(r){return r.json();})'
'.then(function(s){'
'var act=!!(s&&s.active),ph=(s&&s.phase)||curPhase,changed=false;'
'if(act!==cur){'
'cur=act;changed=true;'
# set_market 이 공유 플래그 갱신 + arm(=dot) 호출. 없을 때만 직접 dot.
'if(window.__behive_set_market)window.__behive_set_market(act);else dot();'
# 전환(장 시작/마감) 시에만 1회 전체 재렌더 — day-change 게이트·시간대 라벨 반영.
'if(window.__behive_load)window.__behive_load();'
'}).catch(function(){});'
'},SLOW_MS);'
'}'
# regular↔nxt 는 active 가 둘 다 true 라 phase 로만 감지된다(09:00·15:30 전환).
'if(ph!==curPhase){curPhase=ph;changed=true;}'
# 전환 시 1회 전체 재렌더 — 거래가능 초록점·OHLC 활성 컬럼·day-change 게이트 반영.
# fresh 필수: 패널 캐시(10s)가 전환 직전 HTML을 물고 있고, 주기적 재렌더가 없어 stale 이 굳는다.
'if(changed&&window.__behive_load)window.__behive_load(null,true);'
'schedule(nextWait(s&&s.next_in));'
'}).catch(function(){schedule(SLOW_MS);});'
'}'
# 외부 매매(키움 앱 등) 감지 — 장중 20s 간격 /api/holdings_seq 폴링. 보유 시그니처가 바뀌면
# (앱 직거래·타 기기 체결 등 fill_signal 안 오는 변화 포함) 패널 1회 fresh 갱신. 첫 관측은 기준만.
@@ -10177,9 +10257,10 @@ def render_html() -> str:
# __behive_auto_arm — 수동 새로고침·PTR 완료 후 호출됨. 이제 배지만 재확인(주기 재무장 없음).
'function arm(){dot();}'
'window.__behive_auto_arm=arm;'
'document.addEventListener("visibilitychange",dot);'
# 숨겨진 동안엔 폴링을 건너뛰므로, 돌아오는 즉시 한 번 확인한다(폰에서 15:30을 백그라운드로 넘긴 경우).
'document.addEventListener("visibilitychange",function(){dot();if(document.visibilityState==="visible")schedule(300);});'
'if(window.__behive_set_market)window.__behive_set_market(MARKET);'
'dot();startPoll();startHPoll();'
'dot();schedule(nextWait(NEXT_IN));startHPoll();'
'})();</script>'
)
@@ -11923,11 +12004,18 @@ function refreshStopUI(){
if(stopOpt) stopOpt.disabled = (state.side === 'BUY');
var trailOpt = otSel.querySelector('option[value="TRAILING_STOP"]');
if(trailOpt) trailOpt.disabled = (state.side === 'BUY');
if(state.side === 'BUY' && (otSel.value === 'STOP_LIMIT' || otSel.value === 'TRAILING_STOP')) otSel.value = 'LIMIT';
var armOpt = otSel.querySelector('option[value="TRAILING_ARM"]');
if(armOpt) armOpt.disabled = (state.side === 'BUY');
if(state.side === 'BUY' && (otSel.value === 'STOP_LIMIT' || otSel.value === 'TRAILING_STOP'
|| otSel.value === 'TRAILING_ARM')) otSel.value = 'LIMIT';
var isStop = (otSel.value === 'STOP_LIMIT');
var isTrail = (otSel.value === 'TRAILING_STOP');
var isArm = (otSel.value === 'TRAILING_ARM');
// 계단·최저 매도가·미리보기 UI 는 트레일링과 예약 트레일링이 공유한다 (기준 peak 만 다름).
var isTrail = (otSel.value === 'TRAILING_STOP') || isArm;
var stopRow = $('[data-order-stop-row]');
if(stopRow) stopRow.style.display = isStop ? '' : 'none';
var armRow = $('[data-order-arm-row]');
if(armRow) armRow.style.display = isArm ? '' : 'none';
var trailRow = $('[data-order-trail-row]');
if(trailRow) trailRow.style.display = isTrail ? '' : 'none';
var priceRow = $('[data-order-price-row]');
@@ -11961,6 +12049,20 @@ function trailSteps(){
function trailMinPrice(){
return parseInt(($('[data-order-trail-min]')||{}).value || '0', 10) || 0;
}
function isArmMode(){
var otSel = $('[data-order-type]');
return !!(otSel && otSel.value === 'TRAILING_ARM');
}
function armPrice(){
return parseInt(($('[data-order-arm-price]')||{}).value || '0', 10) || 0;
}
// 계단 계산의 기준가. 예약 트레일링은 발동가, 일반 트레일링은 현재가.
// 서버 handler.propose_trade 의 basis 와 같은 규칙이어야 한다.
function trailBasePeak(){
if(isArmMode()) return armPrice();
return (state.lastCheck && state.lastCheck.cur_price)
|| (state.lastBook && state.lastBook.price) || 0;
}
// 계단별 주수 — 서버 orders/trailing.allocate_step_qty(최대잔여법)와 같은 식이어야 한다.
function allocStepQty(total, weights){
if(!(total > 0)) return weights.map(function(){ return 0; });
@@ -12037,10 +12139,12 @@ function renderTrailPreview(){
var steps = trailSteps();
var minPrice = trailMinPrice();
var cur = (state.lastCheck && state.lastCheck.cur_price) || (state.lastBook && state.lastBook.price) || 0;
if(!steps.length || !(cur > 0)){ el.textContent = ''; return; }
var isArm = isArmMode();
// 계단 기준가 = 예약이면 발동가, 아니면 현재가 (서버 handler 의 basis 와 동일).
var peak = trailBasePeak();
if(!steps.length || !(cur > 0) || !(peak > 0)){ el.textContent = ''; return; }
var qty = parseInt(($('[data-order-qty]')||{}).value || '0', 10) || 0;
var reason = trailBlockReason();
var peak = cur; // 초기 고점 = 등록 시점 현재가 (서버 handler 와 동일)
var qtys = allocStepQty(qty, trailWeights(steps));
var hero, note;
@@ -12051,13 +12155,30 @@ function renderTrailPreview(){
note = '위 입력을 고쳐주세요.';
} else {
var r0 = trailCondPrice(peak, steps[0].pct, minPrice);
var pctFromCur = ((cur - r0.cond) / cur * 100);
hero = '<div class="cap">지금 등록하면 1단계 손절선</div>' +
var pctFromPeak = ((peak - r0.cond) / peak * 100);
hero = '<div class="cap">' +
(isArm ? fmt(peak) + '원에 닿으면 1단계 손절선' : '지금 등록하면 1단계 손절선') +
'</div>' +
'<div class="val">' + fmt(r0.cond) + '원' +
(r0.floorApplied ? '<span class="trail-pv-badge">최저가 적용</span>' : '') + '</div>' +
'<div class="sub">현재가보다 ' + pctFromCur.toFixed(1) + '% 아래 · 여기 닿으면 ' +
'<div class="sub">' + (isArm ? '발동가' : '현재가') + '보다 ' +
pctFromPeak.toFixed(1) + '% 아래 · 여기 닿으면 ' +
(qtys[0] > 0 ? fmt(qtys[0]) + '주' : '') + ' 매도</div>';
note = '고점이 오르면 계단이 <b>통째로 따라 올라갑니다</b>. 내려가지는 않습니다.';
if(isArm){
// 이 기능의 유일한 위험을 화면에서 고지하는 자리다 — 빼지 말 것.
note = '<b>지금은 주문이 나가지 않습니다.</b> 발동가에 닿는 순간 위 계단이 ' +
'자동으로 나갑니다 (PIN 재확인 없음).<br>' +
'<b>오늘 장 마감까지만 유효</b> — 미발동이면 자동 취소됩니다.<br>' +
'실제 손절선은 발동 시점 현재가 기준이라 위 값 이상입니다. ' + note;
// 당일 유효로 바뀐 뒤(2026-08-25)로 상한가 초과 발동가는 '오늘 도달 불가'가 확정이다.
// 막지는 않는다(관리자님 지시) — 무의미하다는 사실만 알린다.
var upLim = (state.lastBook && state.lastBook.upper_limit) || 0;
if(upLim > 0 && peak > upLim){
note = '<b style="color:#e06c62;">⚠️ 오늘 상한가 ' + fmt(upLim) + '원을 넘는 발동가입니다 — ' +
'오늘은 도달할 수 없어 장 마감에 그대로 만료됩니다.</b><br>' + note;
}
}
}
var rows = steps.map(function(s, i){
@@ -12077,7 +12198,14 @@ function renderTrailPreview(){
'주</span><span class="sv">스톱 없음</span></div>';
}
var kv = ['<dt>시작 고점</dt><dd><b>' + fmt(peak) + '원</b><br>등록 시점 현재가</dd>'];
var kv;
if(isArm){
kv = ['<dt>발동가</dt><dd><b>' + fmt(peak) + '원</b><br>도달 시 시작</dd>',
'<dt>현재가</dt><dd>' + fmt(cur) + '원<br>발동까지 +' +
((peak - cur) / cur * 100).toFixed(1) + '%</dd>'];
} else {
kv = ['<dt>시작 고점</dt><dd><b>' + fmt(peak) + '원</b><br>등록 시점 현재가</dd>'];
}
if(minPrice > 0) kv.push('<dt>최저 매도가</dt><dd>' + fmt(minPrice) + '원</dd>');
el.innerHTML = '<div class="trail-pv' + (reason ? ' bad' : '') + '">' +
@@ -12139,7 +12267,7 @@ function renderBook(book){
state.lastBook = book;
// 트레일링 미리보기는 현재가·52주 전고점을 이 응답에서 읽는다 — 호가 갱신마다 다시 그린다.
var otNow = $('[data-order-type]');
if(otNow && otNow.value === 'TRAILING_STOP') renderTrailPreview();
if(otNow && (otNow.value === 'TRAILING_STOP' || otNow.value === 'TRAILING_ARM')) renderTrailPreview();
if(book.name){ var ne = $('[data-order-name]'); if(ne) ne.textContent = book.name; state.name = book.name; }
var ce = $('[data-order-cur-price]'); if(ce) ce.textContent = fmt(book.price)+'원';
var che = $('[data-order-change]');
@@ -12463,6 +12591,49 @@ function closeOpenOrdersModal(){
if(modal.classList.contains('hidden') && (!pinModal || pinModal.classList.contains('hidden'))) document.body.classList.remove('modal-open');
refreshActiveCount();
}
// 대기 예약 행 — 키움 주문이 아니라서 취소 버튼도 다른 경로를 쓴다(data-cancel-arm).
function armRowHtml(r){
var a = r.arm || {};
var cur = r.cur_price || 0;
var gap = (cur > 0)
? (' · 현재가 ' + fmt(cur) + '원 (+' + ((a.trigger_price - cur) / cur * 100).toFixed(1) + '% 필요)')
: '';
var steps = (a.steps || []).map(function(s){
return '−' + s.pct + '% ' + (s.cum >= 100 ? '전량' : '누적 ' + s.cum + '%');
}).join(' · ');
if(a.min_sell_price) steps += ' · 최저 ' + fmt(a.min_sell_price) + '원';
var warn = '';
if(a.firing_at){
var nos = (a.fired_legs || []).map(function(s){ return s.ord_no; })
.filter(function(n){ return !!n; }).join(', ') || '없음';
warn = '<div class="row-line2" style="color:#e06c62;">⚠️ 발동 처리 중단됨 — 접수된 주문: ' +
nos + ' · 키움 미체결 확인 필요</div>';
}
var safeArm = String(a.id || '').replace(/"/g,'&quot;');
return (
'<div class="open-order-row" data-arm-id="' + safeArm + '">' +
'<div class="row-info">' +
'<div class="row-line1">' +
'<span class="badge-side sell">예약 대기</span>' +
'<span>' + (r.name || r.code) + '</span>' +
'<span style="color:#8b8f9a; font-weight:400; font-size:11px;">(' + r.code + ')</span>' +
'</div>' +
'<div class="row-line2">' +
'[' + r.account + '] ' + fmt(r.order_qty) + '주 · 발동가 ' +
fmt(a.trigger_price) + '원 도달 시 시작' + gap +
'</div>' +
'<div class="row-line2" style="color:#d9a84a; overflow-wrap:anywhere;">' + steps + '</div>' +
warn +
'<div class="row-line2">등록 ' + (r.order_time || '') + ' · ' +
(a.expired ? '<b style="color:#e06c62;">만료 — 정리 대기</b>' : '오늘 장 마감까지 유효') +
'</div>' +
'</div>' +
'<div class="row-action">' +
'<button type="button" data-cancel-arm>취소</button>' +
'</div>' +
'</div>'
);
}
function loadOpenOrders(){
var list = openOrdersModal.querySelector('[data-open-orders-list]');
if(!list) return;
@@ -12473,7 +12644,10 @@ function loadOpenOrders(){
list.innerHTML = '<div class="empty">미체결 주문 없음</div>';
return;
}
// 대기 예약을 맨 위로 — 아직 주문이 아니니 미체결과 섞이면 성격이 흐려진다.
rows.sort(function(a, b){ return (b.kind === 'arm') - (a.kind === 'arm'); });
list.innerHTML = rows.map(function(r){
if(r.kind === 'arm') return armRowHtml(r);
var sideCls = r.side === 'BUY' ? 'buy' : 'sell';
var sideLabel = r.side === 'BUY' ? '매수' : '매도';
// 주문유형을 항상 붙인다 — 가격만 쓰면 스톱지정가·트레일링이 지정가처럼 보인다.
@@ -12578,6 +12752,31 @@ function cancelOpenOrder(ordNo, account, btn){
if(btn){ btn.disabled = false; btn.textContent = '취소'; }
});
}
// 대기 예약 취소 — 키움 주문이 아니므로 kt10003 이 아니라 상태파일에서 지운다.
function cancelArm(armId, btn){
if(!confirm('예약 ' + armId + ' 을 취소하시겠습니까?')) return;
if(btn){ btn.disabled = true; btn.textContent = '취소중…'; }
var body = new URLSearchParams();
body.set('arm_id', armId);
fetch('/api/orders/arm_cancel', {
method: 'POST',
headers: {'Content-Type':'application/x-www-form-urlencoded','Accept':'application/json'},
body: body.toString()
}).then(function(r){ return r.json(); })
.then(function(d){
if(d.ok){
showToast(d.message || '✅ 예약 취소됨', 'success', 5000);
loadOpenOrders();
refreshActiveCount();
} else {
showToast(d.message || d.error || '취소 실패', 'error');
if(btn){ btn.disabled = false; btn.textContent = '취소'; }
}
}).catch(function(e){
showToast('취소 오류: ' + e, 'error');
if(btn){ btn.disabled = false; btn.textContent = '취소'; }
});
}
function fetchMarketPhase(){
fetch('/api/market_state').then(function(r){ return r.json(); }).then(function(d){
state.marketActive = !!d.active;
@@ -12620,9 +12819,11 @@ function updateMarketPhaseDisplay(){
// 아니라 입력하는 시점에 막는다.
function trailBlockReason(){
var otSel = $('[data-order-type]');
if(!otSel || otSel.value !== 'TRAILING_STOP') return '';
if(!otSel || (otSel.value !== 'TRAILING_STOP' && otSel.value !== 'TRAILING_ARM')) return '';
var isArm = (otSel.value === 'TRAILING_ARM');
// 계단 검증은 서버 orders/trailing.normalize_steps 와 같은 규칙 — 순서·범위가 어긋나면
// 서버가 TRAIL_STEPS 로 거부하므로, PIN 왕복 전에 여기서 같은 문구로 막는다.
// 예약 트레일링도 같은 계단 규칙을 쓴다 (기준가만 발동가로 바뀐다).
var steps = trailSteps();
if(!steps.length) return '계단을 하나 이상 추가하세요';
if(steps.length > 5) return '계단은 최대 5개입니다';
@@ -12637,13 +12838,24 @@ function trailBlockReason(){
}
var cur = (state.lastCheck && state.lastCheck.cur_price) || (state.lastBook && state.lastBook.price) || 0;
if(!(cur > 0)) return '현재가를 받는 중입니다';
// 가장 얕은 계단이 제일 먼저 발동한다 — 이게 현재가 이상이면 등록 즉시 터진다.
// 예약 트레일링: 발동가가 현재가 위여야 한다. 이미 닿아 있으면 등록 직후 발동하므로
// 서버가 ARM_ALREADY_REACHED 로 거부한다 — 같은 조건을 여기서 먼저 막는다.
var basis = cur;
if(isArm){
var ap = armPrice();
if(!(ap > 0)) return '발동가를 입력하세요';
if(ap <= cur) return '발동가 ' + fmt(ap) + '원이 현재가 ' + fmt(cur) +
'원 이하입니다 (이미 도달 — 일반 트레일링으로 등록하세요)';
basis = ap;
}
// 가장 얕은 계단이 제일 먼저 발동한다 — 이게 기준가 이상이면 시작하는 순간 터진다.
var minPrice = trailMinPrice();
var r = trailCondPrice(cur, steps[0].pct, minPrice); // 초기 고점 = 현재가
if(r.cond >= cur){
var r = trailCondPrice(basis, steps[0].pct, minPrice);
if(r.cond >= basis){
var refW = isArm ? '발동가' : '현재가';
return r.floorApplied
? ('최저 매도가 ' + fmt(minPrice) + '원이 현재가 ' + fmt(cur) + '원보다 높아 즉시 발동됩니다')
: ('1단계 손절선 ' + fmt(r.cond) + '원이 현재가 ' + fmt(cur) + '원 이상이라 즉시 발동됩니다');
? ('최저 매도가 ' + fmt(minPrice) + '원이 ' + refW + ' ' + fmt(basis) + '원보다 높아 즉시 발동됩니다')
: ('1단계 손절선 ' + fmt(r.cond) + '원이 ' + refW + ' ' + fmt(basis) + '원 이상이라 즉시 발동됩니다');
}
var qty = parseInt(($('[data-order-qty]')||{}).value || '0', 10) || 0;
if(qty > 0){
@@ -12722,8 +12934,15 @@ function onSymbolChange(){
$('[data-order-price-input]').value = '';
$('[data-order-qty]').value = '';
var stopInp = $('[data-order-stop-input]'); if(stopInp) stopInp.value = '';
// 종목 변경 시 스톱지정가는 해제 — 이전 종목 트리거가 새 종목에 잘못 적용 방지
var otSelR = $('[data-order-type]'); if(otSelR && otSelR.value === 'STOP_LIMIT'){ otSelR.value = 'LIMIT'; refreshStopUI(); }
// 발동가·최저 매도가는 절대가격이라 종목이 바뀌면 반드시 비운다 — 남으면 엉뚱한 가격으로
// 예약이 나간다(계단은 %라 종목이 바뀌어도 뜻이 유지되므로 그대로 둔다).
var armInp = $('[data-order-arm-price]'); if(armInp) armInp.value = '';
var minInp = $('[data-order-trail-min]'); if(minInp) minInp.value = '';
// 종목 변경 시 스톱지정가·예약 트레일링은 해제 — 이전 종목 트리거가 새 종목에 잘못 적용 방지
var otSelR = $('[data-order-type]');
if(otSelR && (otSelR.value === 'STOP_LIMIT' || otSelR.value === 'TRAILING_ARM')){
otSelR.value = 'LIMIT'; refreshStopUI();
}
var budgetInp = $('[data-order-budget]');
if(budgetInp) budgetInp.value = '';
// 종목 변경 시 새 max로 자동 채움 (SELL: 보유 100%, BUY: 매수가능 max)
@@ -12914,6 +13133,12 @@ function doPropose(){
// 계단 검증·즉시발동 검사는 입력 시점에 trailBlockReason 이 하고, 걸리면 매도 버튼이
// 비활성이라 여기 도달하지 않는다. 서버(TRAIL_STEPS/TRAIL_IMMEDIATE)가 최후 방어선.
}
if(orderType === 'TRAILING_ARM'){
if(state.side !== 'SELL'){ setMsg('예약 트레일링은 매도만 가능합니다', 'error'); return; }
if(armPrice() <= 0){ setMsg('발동가를 입력하세요', 'error'); return; }
// 발동가 ≤ 현재가·계단 오류도 trailBlockReason 이 입력 시점에 버튼을 잠근다.
// 서버(ARM_ALREADY_REACHED/ARM_IMMEDIATE/TRAIL_STEPS)가 최후 방어선.
}
if(orderType === 'STOP_LIMIT'){
if(state.side !== 'SELL'){ setMsg('스톱지정가는 매도만 가능합니다', 'error'); return; }
if(price <= 0){ setMsg('스톱지정가는 매도 단가가 필요합니다', 'error'); return; }
@@ -12938,7 +13163,7 @@ function doPropose(){
// 검증 통과 — 매수/매도 방향 최종 확인 팝업 (오타 방지). 확인 시에만 실제 매매로 진행.
state.pendingOrder = {account: account, orderType: orderType, qty: qty, price: price,
stopPrice: stopPrice, trailSteps: trailStepList,
trailMinPrice: trailMin};
trailMinPrice: trailMin, armPrice: armPrice()};
openOrderConfirm();
}
// 방향 확인 후 실제 매매 진행 — 전량매도 sell-choice 분기 포함.
@@ -12947,8 +13172,9 @@ function doProposeConfirmed(){
var account = po.account, orderType = po.orderType, qty = po.qty, price = po.price;
var lc = state.lastCheck;
// 전량매도(입력 수량 = 매도가능 전량)이고 같은 소유자 그룹의 다른 계좌에도 같은 종목 보유
// → 선택 팝업. 스톱지정가·트레일링은 단일 계좌 예약이라 2계좌 동시 매도 대상 아님.
if(state.side === 'SELL' && orderType !== 'STOP_LIMIT' && orderType !== 'TRAILING_STOP'){
// → 선택 팝업. 스톱지정가·트레일링·예약 트레일링은 단일 계좌 예약이라 대상 아님.
if(state.side === 'SELL' && orderType !== 'STOP_LIMIT' && orderType !== 'TRAILING_STOP'
&& orderType !== 'TRAILING_ARM'){
var fullSell = lc && typeof lc.max_qty === 'number' && lc.max_qty > 0 && qty === lc.max_qty;
var sib = siblingAccount(account);
var sibInfo = (sib && state.accStatus && state.accStatus.byLabel) ? state.accStatus.byLabel[sib] : null;
@@ -12958,7 +13184,8 @@ function doProposeConfirmed(){
return;
}
}
proposeSingle(account, orderType, qty, price, po.stopPrice, po.trailSteps, po.trailMinPrice);
proposeSingle(account, orderType, qty, price, po.stopPrice, po.trailSteps, po.trailMinPrice,
po.armPrice);
}
// ── 매수/매도 방향 확인 모달 ──
// 매수·매도 버튼을 둘 다 띄우고 관리자님이 직접 맞는 방향을 누르게 함(위치 랜덤).
@@ -12969,13 +13196,16 @@ function openOrderConfirm(){
var po = state.pendingOrder; if(!po) return;
var disp = {}; ACCOUNTS.forEach(function(a){ disp[a.label] = a.display; });
var isStop = (po.orderType === 'STOP_LIMIT');
var isTrail = (po.orderType === 'TRAILING_STOP');
var isArm = (po.orderType === 'TRAILING_ARM');
var isTrail = (po.orderType === 'TRAILING_STOP') || isArm;
var typeStr = (po.orderType === 'MARKET') ? '시장가'
: isTrail ? '트레일링 스톱(예약)'
: isArm ? '예약 트레일링(발동 대기)'
: (po.orderType === 'TRAILING_STOP') ? '트레일링 스톱(예약)'
: isStop ? '스톱지정가(예약)' : '지정가';
var curNow = (state.lastCheck && state.lastCheck.cur_price) || (state.lastBook && state.lastBook.price) || 0;
// 트레일링은 단가를 서버가 계산 — 확인 팝업엔 예상 손절선을 보여준다.
var trailPeak = isTrail ? curNow : 0; // 초기 고점 = 등록 시점 현재가
// 기준 고점 = 예약이면 발동가, 일반 트레일링이면 등록 시점 현재가.
var trailPeak = isArm ? (po.armPrice || 0) : (isTrail ? curNow : 0);
var trailStepsPo = (po.trailSteps || []);
var trailRes = (isTrail && trailStepsPo.length)
? trailCondPrice(trailPeak, trailStepsPo[0].pct, po.trailMinPrice)
@@ -13004,12 +13234,27 @@ function openOrderConfirm(){
fmt(poQtys[i]) + '주 ' +
'<span class="muted small">(' + (s.cum >= 100 ? '전량' : '누적 ' + s.cum + '%') + ')</span><br>';
}).join('');
priceLine = '· 시작 고점: <b>' + fmt(trailPeak) + '원</b> <span class="muted small">(등록 시점 현재가)</span><br>' +
var upLimC = (state.lastBook && state.lastBook.upper_limit) || 0;
var armWarn = (isArm && upLimC > 0 && trailPeak > upLimC)
? ('· <b style="color:#e06c62;">⚠️ 오늘 상한가 ' + fmt(upLimC) + '원 초과 — 오늘은 도달 불가, ' +
'장 마감에 만료됩니다</b><br>')
: '';
priceLine = armWarn + (isArm
? ('· 발동가: <b>' + fmt(trailPeak) + '원 도달 시 시작</b> <span class="muted small">(현재가 ' +
fmt(curNow) + '원 · +' + (curNow ? ((trailPeak - curNow) / curNow * 100).toFixed(1) : '0') +
'%)</span><br>')
: ('· 시작 고점: <b>' + fmt(trailPeak) + '원</b> <span class="muted small">(등록 시점 현재가)</span><br>')) +
(po.trailMinPrice > 0 ? ('· 최저 매도가: <b>' + fmt(po.trailMinPrice) + '원</b> <span class="muted small">(' +
(trailRes.floorApplied ? '최저가 적용' : '트레일 적용') + ')</span><br>') : '') +
'· 매도 계단 <b>' + trailStepsPo.length + '단</b> <span class="muted small">(현재가 ' + fmt(curNow) + '원)</span><br>' +
'· 매도 계단 <b>' + trailStepsPo.length + '단</b>' +
(isArm ? ' <span class="muted small">(발동가 기준 예상)</span>'
: ' <span class="muted small">(현재가 ' + fmt(curNow) + '원)</span>') + '<br>' +
stepLines +
'· <span class="muted small">고점이 오르면 계단이 통째로 따라 올라갑니다</span><br>';
(isArm
? '· <b>지금은 주문이 나가지 않습니다</b> <span class="muted small">— 발동가에 닿는 순간 ' +
'PIN 재확인 없이 자동 발주됩니다. 실제 손절선은 발동 시점 현재가 기준이라 위 값 이상입니다.</span><br>' +
'· <b>오늘 장 마감까지만 유효</b> <span class="muted small">(미발동 시 자동 취소)</span><br>'
: '· <span class="muted small">고점이 오르면 계단이 통째로 따라 올라갑니다</span><br>');
} else {
priceLine = '· ' + (isStop ? '매도 단가' : '단가') + ': <b>' + priceStr + '</b><br>';
}
@@ -13046,7 +13291,8 @@ function siblingAccount(label){
var sib = ACCOUNTS.find(function(a){ return a.owner === me.owner && a.label !== label; });
return sib ? sib.label : null;
}
function proposeSingle(account, orderType, qty, price, stopPrice, trailStepList, trailMinPrice){
function proposeSingle(account, orderType, qty, price, stopPrice, trailStepList, trailMinPrice,
armPriceV){
var body = new URLSearchParams();
body.set('account', account);
body.set('side', state.side);
@@ -13058,12 +13304,14 @@ function proposeSingle(account, orderType, qty, price, stopPrice, trailStepList,
if(orderType === 'STOP_LIMIT') body.set('stop_price', String(stopPrice || 0));
// 트레일링은 계단 정의(하락률·누적 비중)와 최저 매도가만 보낸다 —
// 계단별 조건단가·지정가·주수는 서버가 현재가·보유수량 기준으로 계산한다.
if(orderType === 'TRAILING_STOP'){
if(orderType === 'TRAILING_STOP' || orderType === 'TRAILING_ARM'){
// 승인 팝업에서 확정된 계단을 그대로 보낸다 — 팝업이 떠 있는 사이 편집기를 건드려도
// 관리자님이 확인한 내용과 발주가 어긋나지 않는다.
body.set('trail_steps', JSON.stringify(trailStepList || []));
if(trailMinPrice > 0) body.set('min_sell_price', String(trailMinPrice));
}
// 예약 트레일링은 발동가가 승인 대상의 일부 — 계단과 같은 스냅샷에서 온다.
if(orderType === 'TRAILING_ARM') body.set('trigger_price', String(armPriceV || 0));
proposeAndOpenPin('/api/order/propose', body);
}
function doProposeMulti(opts){
@@ -13210,6 +13458,13 @@ if(openOrdersModal) openOrdersModal.addEventListener('click', function(e){
cancelOpenOrder(row.getAttribute('data-ord-no'), row.getAttribute('data-account'), cancelBtn);
return;
}
var armBtn = t.closest && t.closest('[data-cancel-arm]');
if(armBtn){
var arow = armBtn.closest('.open-order-row');
if(!arow) return;
cancelArm(arow.getAttribute('data-arm-id'), armBtn);
return;
}
});
// PIN 모달 click 핸들러
if(pinModal) pinModal.addEventListener('click', function(e){
@@ -13297,7 +13552,7 @@ if(qtyInpEl) qtyInpEl.addEventListener('input', function(){
recalcBudgetFromQty(); renderOrderInfo();
// 계단별 주수는 수량에서 나온다 — 수량이 바뀌면 미리보기도 다시 그린다.
var ot = $('[data-order-type]');
if(ot && ot.value === 'TRAILING_STOP') renderTrailPreview();
if(ot && (ot.value === 'TRAILING_STOP' || ot.value === 'TRAILING_ARM')) renderTrailPreview();
});
var budgetInpEl = $('[data-order-budget]');
if(budgetInpEl) budgetInpEl.addEventListener('input', recalcQtyFromBudget);
@@ -13306,6 +13561,8 @@ var trailPresetEl = $('[data-trail-preset]');
if(trailPresetEl) trailPresetEl.addEventListener('change', applyTrailPreset);
var trailMinEl = $('[data-order-trail-min]');
if(trailMinEl) trailMinEl.addEventListener('input', renderTrailPreview);
var armPriceEl = $('[data-order-arm-price]');
if(armPriceEl) armPriceEl.addEventListener('input', renderTrailPreview);
// 계단 입력·삭제는 행이 동적으로 다시 그려지므로 컨테이너에 위임한다.
var trailStepsEl = $('[data-trail-steps]');
if(trailStepsEl){
@@ -14205,6 +14462,9 @@ class Handler(BaseHTTPRequestHandler):
# 트레일링 예약이면 그 상태를 실어 보낸다 (예약 파일 읽기, 키움 콜 0).
for _r in rows:
_r['trailing'] = _trailing_info_for(_r.get('ord_no', ''))
# 대기 예약은 키움에 주문이 없어 ka10075 에 안 잡힌다 → 합성 행으로 합류시킨다.
# 안 보이는 자동 발주 예약은 그 자체로 사고 요인이고, 이 팝업이 유일한 창구다.
rows.extend(_arm_rows())
self._send_json(200, {'rows': rows, 'count': len(rows)})
except Exception as e:
traceback.print_exc()
@@ -14246,6 +14506,13 @@ class Handler(BaseHTTPRequestHandler):
open_orders_count = len(rows)
except Exception:
traceback.print_exc()
# 대기 예약도 진행중 항목으로 센다 — 그래야 대기 예약만 있을 때도 배지가 뜨고
# 버튼이 팝업을 연다(키움 미체결이 0건이면 '진행 중 거래 없음' 토스트로 끝난다).
try:
from orders import trailing as _trl
open_orders_count += len(_trl.list_arms())
except Exception:
traceback.print_exc()
result['open_orders_count'] = open_orders_count
# 진행중 아이템 수 = 미체결 주문 건수(팝업 행과 동일) + 활성 PIN 카드(있으면 1).
# 종목코드 중복제거 X — 같은 종목 2계좌 매도도 2건으로 표시(팝업과 일치).
@@ -14257,7 +14524,10 @@ class Handler(BaseHTTPRequestHandler):
return
if parsed.path == '/api/market_state':
try:
self._send_json(200, _market_phase_state())
st = _market_phase_state()
# next_in — 다음 phase 경계까지 남은 초. 브라우저가 폴링 시각을 경계에 맞춘다.
st['next_in'] = _secs_to_next_phase()
self._send_json(200, st)
except Exception as e:
traceback.print_exc()
self._send_json(500, {'error': f'market_state failed: {e}'})
@@ -14473,7 +14743,8 @@ class Handler(BaseHTTPRequestHandler):
account = (qs.get('account') or [''])[0].strip()
side = (qs.get('side') or [''])[0].strip().upper()
order_type = (qs.get('order_type') or ['LIMIT'])[0].strip().upper() or 'LIMIT'
if order_type not in ('LIMIT', 'MARKET', 'AGGRESSIVE_LIMIT', 'STOP_LIMIT'):
if order_type not in ('LIMIT', 'MARKET', 'AGGRESSIVE_LIMIT', 'STOP_LIMIT',
'TRAILING_STOP', 'TRAILING_ARM'):
order_type = 'LIMIT'
try:
price = int((qs.get('price') or ['0'])[0].strip() or '0')
@@ -14989,12 +15260,18 @@ class Handler(BaseHTTPRequestHandler):
min_sell_price = int(min_sell_raw) if min_sell_raw else None
except ValueError:
min_sell_price = None
trigger_raw = (params.get('trigger_price') or [''])[0].strip()
try:
trigger_price = int(trigger_raw) if trigger_raw else None
except ValueError:
trigger_price = None
if not account or side not in ('BUY', 'SELL') or not symbol or qty <= 0:
self._send_json(400, {'ok': False, 'error': 'account/side/symbol/qty required'})
return
if order_type not in ('LIMIT', 'MARKET', 'STOP_LIMIT', 'TRAILING_STOP'):
if order_type not in ('LIMIT', 'MARKET', 'STOP_LIMIT', 'TRAILING_STOP', 'TRAILING_ARM'):
self._send_json(400, {'ok': False,
'error': 'order_type must be LIMIT|MARKET|STOP_LIMIT|TRAILING_STOP'})
'error': 'order_type must be '
'LIMIT|MARKET|STOP_LIMIT|TRAILING_STOP|TRAILING_ARM'})
return
if order_type == 'LIMIT' and (price is None or price <= 0):
self._send_json(400, {'ok': False, 'error': 'LIMIT requires positive price'})
@@ -15006,12 +15283,13 @@ class Handler(BaseHTTPRequestHandler):
if price is None or price <= 0 or stop_price is None or stop_price <= 0:
self._send_json(400, {'ok': False, 'error': '스톱지정가는 단가와 조건단가가 모두 필요'})
return
if order_type == 'TRAILING_STOP':
# 계단별 조건단가·지정가·주수는 handler 가 현재가·보유수량 기준으로 계산 —
# 여기선 계단 정의(하락률·누적 비중)만 받는다. 형식 검증은 handler 가
# orders/trailing.normalize_steps 로 하므로 여기선 최소한만 본다.
if order_type in ('TRAILING_STOP', 'TRAILING_ARM'):
# 계단별 조건단가·지정가·주수는 handler 가 계산 — 여기선 계단 정의(하락률·누적
# 비중)만 받는다. 형식 검증은 handler 가 orders/trailing.normalize_steps 로
# 하므로 여기선 최소한만 본다. 예약 트레일링은 발동가가 하나 더 붙는다.
kind_label = '예약 트레일링' if order_type == 'TRAILING_ARM' else '트레일링 스톱'
if side != 'SELL':
self._send_json(400, {'ok': False, 'error': '트레일링 스톱은 매도만 지원'})
self._send_json(400, {'ok': False, 'error': f'{kind_label}은 매도만 지원'})
return
if not isinstance(trail_steps, list) or not trail_steps:
self._send_json(400, {'ok': False, 'error': '매도 계단을 입력하세요'})
@@ -15019,6 +15297,9 @@ class Handler(BaseHTTPRequestHandler):
if min_sell_price is not None and min_sell_price <= 0:
self._send_json(400, {'ok': False, 'error': '최저 매도가는 0보다 커야 합니다'})
return
if order_type == 'TRAILING_ARM' and (trigger_price is None or trigger_price <= 0):
self._send_json(400, {'ok': False, 'error': '발동가를 입력하세요'})
return
try:
if not symbol_name:
import kiwoom_client as kc
@@ -15035,8 +15316,11 @@ class Handler(BaseHTTPRequestHandler):
qty=qty, order_type=order_type,
price=(price if order_type in ('LIMIT', 'STOP_LIMIT') else None),
stop_price=(stop_price if order_type == 'STOP_LIMIT' else None),
trail_steps=(trail_steps if order_type == 'TRAILING_STOP' else None),
min_sell_price=(min_sell_price if order_type == 'TRAILING_STOP' else None),
trail_steps=(trail_steps
if order_type in ('TRAILING_STOP', 'TRAILING_ARM') else None),
min_sell_price=(min_sell_price
if order_type in ('TRAILING_STOP', 'TRAILING_ARM') else None),
trigger_price=(trigger_price if order_type == 'TRAILING_ARM' else None),
)
if res.get('ok'):
# 매수/매도 미리보기 카드 메시지·PIN 메시지 모두 텔레그램 발송 X.
@@ -15180,6 +15464,24 @@ class Handler(BaseHTTPRequestHandler):
_bump_panel_signal() # 미체결 취소 → 배지 제거 반영
self._send_json(200 if res.get('ok') else 400, res)
return
if self.path == '/api/orders/arm_cancel':
# 대기 예약(예약 트레일링) 취소. 키움 주문이 아니라 상태파일에서 지운다.
# 텔레그램 발송 없음 — 실주문 취소가 아니므로 토스트로 충분하다.
arm_id = (params.get('arm_id') or [''])[0].strip()
if not arm_id:
self._send_json(400, {'ok': False, 'error': 'arm_id required'})
return
try:
from orders import handler
res = handler.cancel_trailing_arm(arm_id)
except Exception as e:
traceback.print_exc()
self._send_json(500, {'ok': False, 'error': f'arm cancel failed: {e}'})
return
if res.get('ok'):
_bump_panel_signal() # 대기 예약 취소 → 배지 반영
self._send_json(200 if res.get('ok') else 400, res)
return
if self.path == '/api/order/cancel':
# 거래 모달 닫기 시 활성 카드 정리 (자동). 텔레그램 알림 X — 웹 자동 cancel은 사용자가 명시 액션 아님.
try:
+69 -3
View File
@@ -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}가 취소되었습니다.'
@@ -130,6 +130,15 @@ def _safe_broker_executions(account_label: str, now: datetime) -> tuple[Optional
return executions, None
def nxt_eligible(symbol: str) -> bool:
"""NXT 거래가능 여부 (종목코드만으로). 캐시 읽기라 API 콜 0.
collect_market_data 를 거치지 않는 호출자용 — 트레일링 감시 루프가 대기 예약을
발동시킬 때 라우팅(_AL/_KRX)을 정하는 데 쓴다.
"""
return _nxt_eligible(symbol, {})
def collect_market_data(account_label: str, symbol: str, side: str, qty: int) -> dict:
now = datetime.now(KST)
quote = _safe_quote(symbol, account_label)
@@ -89,6 +89,17 @@ def session_at(now: datetime) -> str:
return 'CLOSED'
def after_trading_day(now: datetime) -> bool:
"""그날 거래 세션이 전부 끝났는가 (NXT 애프터 마감 = 20:00 이후).
⚠️ `session_at(now) == 'CLOSED'` 로 대신하면 안 된다 — 그 판정은 **장 시작 전 새벽**과
**09:00:00~09:00:30 틈**(NXT 프리 종료와 정규장 시작 사이)에도 CLOSED 를 돌려주므로,
당일 유효기간을 재는 데 쓰면 아침에 멀쩡한 예약이 만료로 잡힌다.
"""
th = _limits()['trading_hours']
return now.replace(tzinfo=None).time() >= _parse_hms(th['nxt_after_end'])
def is_today_holiday(now: datetime) -> bool:
th = _limits()['trading_hours']
rel = th.get('holiday_state_file')
@@ -103,6 +114,11 @@ def is_today_holiday(now: datetime) -> bool:
return False
today = now.strftime('%Y-%m-%d')
holidays = data.get('holidays') if isinstance(data, dict) else data
# holiday_sync.py 가 쓰는 실제 형태는 {날짜: 이름} dict 다. list 만 받던 시절의
# 코드가 남아 있어 dict 를 만나면 조용히 False 를 반환했고(= 휴장일 가드가 통째로
# 무력화), 2026-08-25 예약 트레일링 작업에서 발견해 dict 를 먼저 처리하게 했다.
if isinstance(holidays, dict):
return today in holidays
if not isinstance(holidays, list):
return False
for h in holidays:
@@ -504,7 +520,7 @@ def validate_request(request: dict, market_data: dict) -> Result:
if request['side'] not in ('BUY', 'SELL'):
return Result.REJECT('INVALID_SIDE', f'잘못된 방향: {request["side"]}')
if request['order_type'] not in ('LIMIT', 'MARKET', 'AGGRESSIVE_LIMIT', 'STOP_LIMIT',
'TRAILING_STOP'):
'TRAILING_STOP', 'TRAILING_ARM'):
return Result.REJECT('INVALID_ORDER_TYPE', f'잘못된 주문방식: {request["order_type"]}')
r = validate_trading_hours(market_data['now'], market_data.get('is_holiday', False),
market_data.get('nxt_eligible', False))
@@ -548,6 +564,20 @@ def validate_request(request: dict, market_data: dict) -> Result:
return Result.REJECT('STOP_TRIGGER_NOT_BELOW',
f'조건단가 {stop_price:,}원 ≥ 현재가 {cur:,}원 — '
f'하락 시 매도(스톱)는 현재가보다 낮게 설정해야 함')
if request['order_type'] == 'TRAILING_ARM':
# 예약 트레일링은 지금 나가는 주문이 아니라 "발동가에 닿으면 시작" 하는 예약이다.
# 그래서 스톱지정가와 두 가지 검증을 일부러 건너뛴다:
# · 가격제한폭 — 발동가는 오늘 상한가 위여도 통과시킨다. 실제 발주는 발동 시점의
# 현재가로 계산되므로 그때 검증된다. arm 이 당일 유효가 된 뒤로는 상한가 초과가
# "오늘 도달 불가 = 그냥 만료" 를 뜻하지만, **막지 않고 경고만** 한다
# (2026-08-25 관리자님 결정). 경고는 승인 카드(card.format_card)와 웹
# 미리보기·확인 팝업 3곳에 있다 — 여기서 거부로 바꾸지 말 것.
# · 조건단가 < 현재가 — 발동가는 정의상 현재가보다 위다. 이 검사를 태우면 전부 거부된다.
# 아래 매도 보유수량 검증은 그대로 받는다 — 지금 없는 종목을 예약해봐야 무의미하다.
if request['side'] != 'SELL':
return Result.REJECT('ARM_SELL_ONLY', '예약 트레일링은 매도 전용')
if not request.get('trigger_price'):
return Result.REJECT('ARM_TRIGGER', '발동가를 입력하세요')
if request['side'] == 'BUY':
basis = None
if request['order_type'] == 'MARKET':
+192 -41
View File
@@ -167,11 +167,13 @@ def propose_trade(
stop_price: Optional[int] = None,
trail_steps: Optional[list] = None,
min_sell_price: Optional[int] = None,
trigger_price: Optional[int] = None,
) -> dict:
sidecar.guard_or_raise()
_sweep_expired_and_notify()
if order_type not in ('LIMIT', 'MARKET', 'AGGRESSIVE_LIMIT', 'STOP_LIMIT', 'TRAILING_STOP'):
if order_type not in ('LIMIT', 'MARKET', 'AGGRESSIVE_LIMIT', 'STOP_LIMIT',
'TRAILING_STOP', 'TRAILING_ARM'):
return {'ok': False,
'message': card.format_rejected('INVALID_ORDER_TYPE', f'잘못된 주문방식: {order_type}')}
if side not in ('BUY', 'SELL'):
@@ -186,12 +188,19 @@ def propose_trade(
'message': card.format_rejected('STOP_INPUT',
'스톱지정가는 지정가와 조건단가(트리거)가 모두 필요')}
norm_steps = None
if order_type == 'TRAILING_STOP':
if order_type in ('TRAILING_STOP', 'TRAILING_ARM'):
# 트레일링은 계단 정의(하락률·누적 비중)만 입력받고, 계단별 조건단가·지정가·주수는
# 현재가와 보유수량 기준으로 자동 계산한다 (md 수집 후).
#
# 예약 트레일링(TRAILING_ARM)은 같은 입력에 발동가가 하나 더 붙고, 계단 계산의 기준을
# 현재가 대신 발동가로 쓴다. 그 외 검증은 전부 공유한다 — 발동하면 결국 같은 물건이
# 되므로 여기서 갈라놓으면 한쪽만 고쳐지는 사고가 난다.
is_arm = order_type == 'TRAILING_ARM'
kind = '예약 트레일링' if is_arm else '트레일링 스톱'
if side != 'SELL':
return {'ok': False,
'message': card.format_rejected('STOP_SELL_ONLY', '트레일링 스톱은 매도 전용')}
'message': card.format_rejected(
'ARM_SELL_ONLY' if is_arm else 'STOP_SELL_ONLY', f'{kind}은 매도 전용')}
try:
norm_steps = trailing.normalize_steps(trail_steps or [])
except (ValueError, KeyError, TypeError) as e:
@@ -201,11 +210,15 @@ def propose_trade(
# 계단별 주수는 보유수량을 나눠 만든다 — 예산 환산이 끼면 기준이 두 개가 된다.
return {'ok': False,
'message': card.format_rejected('TRAIL_BUDGET',
'트레일링은 수량으로만 지정 (예산 환산 불가)')}
f'{kind}은 수량으로만 지정 (예산 환산 불가)')}
if min_sell_price is not None and min_sell_price <= 0:
return {'ok': False,
'message': card.format_rejected('TRAIL_MIN_PRICE',
'최저 매도가는 0보다 커야 함 (제한 없으면 비우세요)')}
if is_arm and (not trigger_price or trigger_price <= 0):
return {'ok': False,
'message': card.format_rejected('ARM_TRIGGER', '발동가를 입력하세요')}
# 같은 계좌·종목에 활성 트레일링과 대기 예약이 함께 있으면 안 된다 — 어느 쪽이든 먼저 취소.
dup = trailing.find_by_symbol(account, symbol)
if dup:
dup_steps = dup.get('steps') or []
@@ -216,6 +229,14 @@ def propose_trade(
f'{symbol_name or symbol} 트레일링 예약이 이미 있음 '
f'({dup["id"]} · {len(dup_steps)}단 · 1단계 손절 {top_uv:,}원) '
f'— 먼저 취소하세요')}
dup_arm = trailing.find_arm_by_symbol(account, symbol)
if dup_arm:
return {'ok': False,
'message': card.format_rejected(
'TRAIL_DUPLICATE',
f'{symbol_name or symbol} 예약 트레일링이 이미 있음 '
f'({dup_arm["id"]} · 발동가 {dup_arm["trigger_price"]:,}원 · '
f'{len(dup_arm.get("steps") or [])}단) — 먼저 취소하세요')}
if budget is not None and qty is not None:
return {'ok': False,
'message': card.format_rejected('AMBIGUOUS_INPUT',
@@ -230,17 +251,37 @@ def propose_trade(
trail_final = None
trail_peak = None
if order_type == 'TRAILING_STOP':
if order_type in ('TRAILING_STOP', 'TRAILING_ARM'):
is_arm = order_type == 'TRAILING_ARM'
# 현재가를 못 받으면 손절선을 정할 수 없어 진행 불가 (호가 fallback 금지 — 기준이 흔들림).
cur = md.get('current_price') or 0
if cur <= 0:
return {'ok': False,
'message': card.format_rejected('TRAIL_NO_PRICE',
'현재가를 못 받아 트레일링 손절선을 정할 수 없음')}
# 초기 고점은 등록 시점 현재가로 고정. 과거 고점(52주·매수후)을 쓰면 손절선이 현재가
# 위로 올라가 등록 즉시 발동한다 — 손실 종목은 아예 등록 불가, 이익 종목은 현재가와
# 결과가 같아 옵션 자체를 제거했다(2026-07-30 실측 근거로 관리자님 결정).
trail_peak = cur
'message': card.format_rejected(
'TRAIL_NO_PRICE',
'현재가를 못 받아 발동가 도달 여부를 판정할 수 없음' if is_arm
else '현재가를 못 받아 트레일링 손절선을 정할 수 없음')}
if is_arm:
# 이미 닿아 있으면 '기다렸다가'라는 이 주문유형의 존재 이유가 사라진다 —
# 등록하는 순간 다음 감시(1분 내)에 바로 발동한다. 그럴 거면 일반 트레일링이 맞다.
if cur >= trigger_price:
return {'ok': False,
'message': card.format_rejected(
'ARM_ALREADY_REACHED',
f'현재가 {cur:,}원이 이미 발동가 {trigger_price:,}원 이상입니다 — '
f'발동가를 올리시거나 일반 트레일링 스톱으로 등록하세요')}
# 계단 계산 기준 = 발동가. 실제 발주는 발동 시점 현재가로 다시 계산하지만,
# 여기서 미리 재보는 이유는 ①카드에 예상 손절선·주수를 보이고 ②발동해도 못 쓰는
# 조합(수량 부족·즉시발동)을 지금 걸러내기 위해서다.
# 발동 시점엔 현재가 >= 발동가 이므로 실제 손절선은 이 미리보기 이상이다
# — 관리자님이 승인한 것보다 불리해지는 방향으로는 움직이지 않는다.
basis = trigger_price
else:
# 초기 고점은 등록 시점 현재가로 고정. 과거 고점(52주·매수후)을 쓰면 손절선이 현재가
# 위로 올라가 등록 즉시 발동한다 — 손실 종목은 아예 등록 불가, 이익 종목은 현재가와
# 결과가 같아 옵션 자체를 제거했다(2026-07-30 실측 근거로 관리자님 결정).
basis = cur
trail_peak = basis
try:
leveled = trailing.compute_step_levels(trail_peak, norm_steps, min_sell_price)
except ValueError as e:
@@ -257,16 +298,25 @@ def propose_trade(
# 고점=현재가로 고정된 뒤로는 최저 매도가가 현재가보다 높은 경우만 남는다
# (트레일 폭은 항상 현재가 아래로 내려감).
top = trail_final[0]
if top['cond_uv'] >= cur:
if top['cond_uv'] >= basis:
ref = '발동가' if is_arm else '현재가'
when = '발동하자마자' if is_arm else '등록 즉시'
if top['floor_applied']:
msg = (f'최저 매도가 {min_sell_price:,}원이 현재가 {cur:,}원보다 높아 '
f'등록 즉시 발동됩니다 — 현재가보다 낮게 설정하세요')
msg = (f'최저 매도가 {min_sell_price:,}원이 {ref} {basis:,}원보다 높아 '
f'{when} 발동됩니다 — {ref}보다 낮게 설정하세요')
else:
msg = (f'1단계 손절선 {top["cond_uv"]:,}원이 현재가 {cur:,}원 이상이라 '
f'등록 즉시 발동됩니다')
return {'ok': False, 'message': card.format_rejected('TRAIL_IMMEDIATE', msg)}
price = top['ord_uv']
stop_price = top['cond_uv']
msg = (f'1단계 손절선 {top["cond_uv"]:,}원이 {ref} {basis:,}원 이상이라 '
f'{when} 발동됩니다')
return {'ok': False,
'message': card.format_rejected(
'ARM_IMMEDIATE' if is_arm else 'TRAIL_IMMEDIATE', msg)}
if is_arm:
# 예약 단계에선 나가는 주문이 없다 — 발주가는 발동 시점에 정해진다.
price = None
stop_price = None
else:
price = top['ord_uv']
stop_price = top['cond_uv']
budget_conversion = None
if budget is not None:
@@ -300,6 +350,13 @@ def propose_trade(
request['trail_steps'] = trail_final
request['min_sell_price'] = min_sell_price
request['trail_peak'] = trail_peak
elif order_type == 'TRAILING_ARM':
# price/stop_price 를 넣지 않는다 — 지금 나가는 주문이 없고, 넣으면 validate_request 의
# 가격제한폭·"조건단가 < 현재가" 검사에 걸린다(둘 다 예약엔 무의미하다).
request['trigger_price'] = trigger_price
request['trail_steps'] = trail_final
request['min_sell_price'] = min_sell_price
request['trail_peak'] = trail_peak
r = guards.validate_request(request, md)
if not r.ok:
@@ -352,6 +409,15 @@ def propose_trade(
payload['trail_steps'] = trail_final
payload['trail_peak'] = trail_peak
payload['min_sell_price'] = min_sell_price
elif order_type == 'TRAILING_ARM':
# 승인 시점에 확정: 계좌·종목·수량·발동가·계단 정의·최저 매도가.
# 발동 시점에 정해짐: 고점(그때의 현재가)·계단별 조건단가/지정가/주수·라우팅.
# ⚠️ trail_steps 에는 레벨 없는 정규화 계단만 넣는다 — 발동 경로가 미리보기 가격을
# 실수로 재사용하지 못하게 구조적으로 막는다. 미리보기는 별도 키(trail_preview).
payload['trigger_price'] = trigger_price
payload['trail_steps'] = norm_steps
payload['trail_preview'] = trail_final
payload['min_sell_price'] = min_sell_price
try:
pending = _pin_store.issue(account, payload)
@@ -538,6 +604,8 @@ def submit_with_pin(pin_input: str, dry_run: bool = True) -> dict:
return _submit_multi_legs(card_obj, dry_run)
if p['order_type'] == 'TRAILING_STOP':
return _submit_trailing_steps(card_obj, dry_run)
if p['order_type'] == 'TRAILING_ARM':
return _register_trailing_arm(card_obj, dry_run)
submit_order_type = 'LIMIT' if p['order_type'] == 'AGGRESSIVE_LIMIT' else p['order_type']
res = kiwoom_order.submit(
@@ -576,6 +644,108 @@ def submit_with_pin(pin_input: str, dry_run: bool = True) -> dict:
'detail': res}
def _register_trailing_arm(card_obj, dry_run: bool) -> dict:
"""예약 트레일링 카드 — 키움 호출 0. 발동가 대기 예약 1건을 파일에 쓰고 끝난다.
PIN 은 여기서 소비된다. 발동 시점엔 PIN 이 없다(관리자님 명시 승인) — 그래서 승인 대상에
발동가까지 포함되고, 감시 루프는 그 확정된 내용을 조건 도달 시 집행만 한다.
"""
p = card_obj.payload
name = p.get('symbol_name', p['symbol'])
info = {'card_id': card_obj.card_id, 'account': p['account'], 'symbol': p['symbol'],
'symbol_name': name, 'qty': p['qty'], 'trigger_price': p['trigger_price'],
'min_sell_price': p.get('min_sell_price'), 'steps': p.get('trail_steps') or [],
'dry_run': dry_run, 'arm_id': None}
if dry_run:
return {'ok': True, 'message': card.format_arm_registered(info)}
try:
arm = trailing.register_arm(
account=p['account'], symbol=p['symbol'], symbol_name=name,
qty=p['qty'], trigger_price=p['trigger_price'],
steps=p.get('trail_steps') or [],
min_sell_price=p.get('min_sell_price'), card_id=card_obj.card_id)
except Exception as e:
# 주문이 나간 게 없으므로 여기선 그냥 거부로 끝내면 된다 (트레일링 등록 실패와 달리
# 되돌릴 외부 효과가 없다).
ledger.append('arm_fire_failed', {'card_id': card_obj.card_id,
'error': repr(e), 'stage': 'register', **p})
return {'ok': False,
'message': card.format_rejected('ARM_REGISTER_FAILED',
f'{name} 예약 등록 실패 — {e}')}
info['arm_id'] = arm['id']
ledger.append('arm_registered', {'card_id': card_obj.card_id, 'arm_id': arm['id'], **p})
return {'ok': True, 'message': card.format_arm_registered(info), 'arm_id': arm['id']}
def cancel_trailing_arm(arm_id: str) -> dict:
"""대기 예약 취소. PIN 없음 — 취소는 안전한 방향이다(cancel_open_order 와 같은 판례).
⚠️ firing 마킹이 걸린 예약은 이미 키움에 스톱주문이 나갔을 수 있다. 그 주문까지 자동으로
취소하지는 않는다 — 승인 범위를 넘는 매매 API 자동 호출이 된다. 대신 접수된 주문번호를
응답에 실어 관리자님이 미체결 목록에서 직접 처리하게 한다.
"""
sidecar.guard_or_raise()
arm = trailing.get_arm(arm_id)
if arm is None:
return {'ok': False, 'message': f'대기 예약 없음 ({arm_id})'}
fired = arm.get('fired_legs') or []
gone = trailing.remove_arm(arm_id, 'manual_cancel')
if gone is None:
return {'ok': False, 'message': f'대기 예약 없음 ({arm_id})'}
ledger.append('arm_canceled', {'arm_id': arm_id, 'account': arm['account'],
'symbol': arm['symbol'], 'qty': arm['qty'],
'trigger_price': arm['trigger_price']})
msg = (f'🗑 예약 트레일링 취소 ({arm_id}) — {arm["symbol_name"]} '
f'{arm["qty"]:,}주 · 발동가 {arm["trigger_price"]:,}원')
if fired:
nos = ', '.join(s.get('ord_no', '') for s in fired if s.get('ord_no'))
msg += (f'\n⚠️ 발동 처리 중 접수된 주문이 있습니다: {nos}\n'
f'미체결 목록에서 별도로 취소해 주세요 (자동 취소하지 않습니다).')
return {'ok': True, 'message': msg, 'placed_orders': fired}
def place_trailing_legs(account: str, symbol: str, steps: list, routing_suffix: str,
card_id: Optional[str], dry_run: bool,
on_placed=None) -> tuple:
"""계단별 스톱지정가 접수. 반환 (placed, failed, details).
얕은 계단(먼저 발동할 것)부터 순서대로 낸다 — 중간에 막혀도 가장 가까운 방어선이 먼저 걸린다.
한 레그가 실패해도 나머지는 계속 시도한다. 이미 나간 주문을 되돌리지 않는 이유는 취소 자체도
실패할 수 있어 상태가 더 불분명해지기 때문이다 — 대신 성공·실패를 그대로 호출측에 돌려준다.
`on_placed(leg)` 는 레그 하나가 접수될 때마다 즉시 호출된다. 예약 트레일링 발동 경로가
"어느 레그까지 실제로 나갔는지" 를 디스크에 미리 적는 데 쓴다(중간에 죽었을 때의 복구 단서).
실주문이 나가는 유일한 계단 발주 경로다 — 등록 즉시 트레일링과 예약 발동이 이 함수를
공유해야 두 벌로 갈라지지 않는다.
"""
placed, failed, details = [], [], []
for s in steps:
res = kiwoom_order.submit(
account_label=account,
side='SELL',
symbol=symbol,
qty=s['qty'],
price=s['ord_uv'],
order_type='STOP_LIMIT',
routing_suffix=routing_suffix,
dry_run=dry_run,
card_id=card_id,
stop_price=s['cond_uv'],
)
details.append(res)
if res['ok']:
# dry_run 은 주문번호가 없다 — 예약 등록 없이 접수될 내용만 확인한다.
leg = dict(s, ord_no=res.get('ord_no', ''))
placed.append(leg)
if on_placed is not None:
on_placed(leg)
else:
failed.append(dict(s, reason=res.get('reason', 'UNKNOWN'),
error=str(res.get('error', ''))))
return placed, failed, details
def _submit_trailing_steps(card_obj, dry_run: bool) -> dict:
"""트레일링 계단 카드 — 계단마다 스톱주문을 내고 예약 1건으로 묶어 등록한다.
@@ -592,28 +762,9 @@ def _submit_trailing_steps(card_obj, dry_run: bool) -> dict:
p = card_obj.payload
steps = p.get('trail_steps') or []
name = p.get('symbol_name', p['symbol'])
placed, failed, details = [], [], []
for s in steps:
res = kiwoom_order.submit(
account_label=p['account'],
side='SELL',
symbol=p['symbol'],
qty=s['qty'],
price=s['ord_uv'],
order_type='STOP_LIMIT',
routing_suffix=p['routing_suffix'],
dry_run=dry_run,
card_id=card_obj.card_id,
stop_price=s['cond_uv'],
)
details.append(res)
if res['ok']:
# dry_run 은 주문번호가 없다 — 예약 등록 없이 접수될 내용만 확인한다.
placed.append(dict(s, ord_no=res.get('ord_no', '')))
else:
failed.append(dict(s, reason=res.get('reason', 'UNKNOWN'),
error=str(res.get('error', ''))))
placed, failed, details = place_trailing_legs(
account=p['account'], symbol=p['symbol'], steps=steps,
routing_suffix=p['routing_suffix'], card_id=card_obj.card_id, dry_run=dry_run)
if not placed:
return {'ok': False,
@@ -89,7 +89,7 @@
"ledger": {
"log_file": "state/order_log.jsonl",
"events": ["card_issued", "pin_issued", "amended", "approved", "rejected", "expired", "canceled", "submitted", "filled", "partial", "failed", "dryrun", "cancel_submitted", "cancel_rejected", "cancel_confirmed", "cancel_unconfirmed_timeout", "modify_submitted", "modify_rejected"],
"events": ["card_issued", "pin_issued", "amended", "approved", "rejected", "expired", "canceled", "submitted", "filled", "partial", "failed", "dryrun", "cancel_submitted", "cancel_rejected", "cancel_confirmed", "cancel_unconfirmed_timeout", "modify_submitted", "modify_rejected", "trailing_register_failed", "arm_registered", "arm_fired", "arm_fire_failed", "arm_canceled"],
"mask_token_in_logs": true
},
@@ -0,0 +1,248 @@
"""예약 트레일링(TRAILING_ARM) propose·승인 경로 회귀 테스트 (2026-08-25).
datasource·sidecar·pin_store·ledger 를 mock 해서 키움을 전혀 건드리지 않는다.
가장 중요한 확인은 **거부 케이스에서 카드가 발행되지 않는 것** — 카드가 나가면 PIN 한 번으로
발동 조건까지 확정되므로, 걸러야 할 입력이 카드 단계를 통과하면 안 된다.
"""
from __future__ import annotations
import shutil
import tempfile
import unittest
from datetime import datetime, timedelta, timezone
from pathlib import Path
from unittest import mock
from orders import handler, ledger
from orders import trailing as t
KST = timezone(timedelta(hours=9))
STEPS = [{'pct': 10, 'cum': 20}, {'pct': 20, 'cum': 50}, {'pct': 30, 'cum': 100}]
def _md(cur=70000):
return {
'now': datetime(2026, 8, 25, 10, 0, tzinfo=KST),
'is_holiday': False,
'nxt_eligible': True,
'current_price': cur,
'prev_close': cur,
'upper_limit': int(cur * 1.3),
'lower_limit': int(cur * 0.7),
'halt': False,
'vi': False,
'orderbook': {'asks': [{'price': cur + 100, 'qty': 200}],
'bids': [{'price': cur, 'qty': 200}]},
'broker_executions': [],
'broker_query_error': None,
'position_qty': 100,
'balance_d2': 5_000_000,
'stock_meta': None,
}
class ArmProposeTests(unittest.TestCase):
def setUp(self):
self.tmp = Path(tempfile.mkdtemp()) / 'trailing_stops.json'
self._orig = (t.STATE_FILE, t._LOCK_FILE)
t.STATE_FILE = self.tmp
t._LOCK_FILE = self.tmp.with_suffix('.json.lock')
self.collect = mock.patch.object(handler.datasource, 'collect_market_data',
return_value=_md()).start()
mock.patch.object(handler.sidecar, 'guard_or_raise', return_value=None).start()
mock.patch.object(handler, '_sweep_expired_and_notify', return_value=None).start()
mock.patch.object(handler.ledger, 'append', return_value=None).start()
mock.patch.object(ledger, 'last_terminal_event', return_value=None).start()
mock.patch.object(ledger, 'last_event_for_symbol', return_value=None).start()
fake = mock.MagicMock()
fake.card_id, fake.pin, fake.expiry_seconds, fake.account_label = 'TEST', '1234', 120, '일반'
self.issue = mock.patch.object(handler._pin_store, 'issue', return_value=fake).start()
self.addCleanup(mock.patch.stopall)
def tearDown(self):
t.STATE_FILE, t._LOCK_FILE = self._orig
shutil.rmtree(self.tmp.parent, ignore_errors=True)
def _propose(self, **kw):
base = dict(account='일반', side='SELL', symbol='005930', symbol_name='삼성전자',
qty=100, order_type='TRAILING_ARM', trail_steps=STEPS,
trigger_price=80000)
base.update(kw)
return handler.propose_trade(**base)
def _assert_rejected(self, res, code):
self.assertFalse(res['ok'], msg=res.get('message'))
self.assertIn(code, res['message'])
self.issue.assert_not_called() # 거부는 카드를 남기지 않아야 한다
# ── 통과 경로 ──
def test_valid_arm_issues_card_with_definition_only(self):
res = self._propose(min_sell_price=60000)
self.assertTrue(res['ok'], msg=res.get('message'))
payload = self.issue.call_args[0][1]
self.assertEqual(payload['order_type'], 'TRAILING_ARM')
self.assertEqual(payload['trigger_price'], 80000)
self.assertIsNone(payload['price']) # 나가는 주문이 없다
# trail_steps 는 정의만, 미리보기는 별도 키 — 발동 경로가 옛 가격을 재사용하지 못하게.
self.assertNotIn('cond_uv', payload['trail_steps'][0])
self.assertIn('cond_uv', payload['trail_preview'][0])
def test_preview_uses_trigger_not_current_price(self):
res = self._propose()
self.assertTrue(res['ok'], msg=res.get('message'))
payload = self.issue.call_args[0][1]
expect = t.compute_step_levels(80000, t.normalize_steps(STEPS), None)
self.assertEqual([s['cond_uv'] for s in payload['trail_preview']],
[s['cond_uv'] for s in expect])
def test_trigger_above_todays_upper_limit_is_allowed(self):
"""발동가는 가격제한폭 검증을 받지 않는다 — 실제 발주는 발동 시점 현재가로 계산된다.
arm 이 당일 유효가 된 뒤로는 '오늘 상한가 초과 = 오늘 도달 불가'지만 **막지 않고
경고만** 한다(2026-08-25 관리자님 결정). 통과가 의도된 동작이라 고정해 둔다.
"""
res = self._propose(trigger_price=200000) # 현재가 70,000 · 상한가 91,000
self.assertTrue(res['ok'], msg=res.get('message'))
def test_trigger_above_upper_limit_warns_on_card(self):
"""막지는 않되 승인 전에 '오늘 도달 불가'를 반드시 알린다."""
res = self._propose(trigger_price=200000)
self.assertTrue(res['ok'], msg=res.get('message'))
self.assertIn('오늘 상한가', res['card_message'])
self.assertIn('만료', res['card_message'])
# 상한가 이내면 경고가 없어야 한다 (남발 금지)
res2 = self._propose(trigger_price=85000) # 상한가 91,000 이내
self.assertTrue(res2['ok'], msg=res2.get('message'))
self.assertNotIn('오늘 상한가', res2['card_message'])
# ── 거부 경로 ──
def test_buy_rejected(self):
self._assert_rejected(self._propose(side='BUY'), 'ARM_SELL_ONLY')
def test_missing_trigger_rejected(self):
self._assert_rejected(self._propose(trigger_price=None), 'ARM_TRIGGER')
def test_zero_trigger_rejected(self):
self._assert_rejected(self._propose(trigger_price=0), 'ARM_TRIGGER')
def test_already_reached_rejected(self):
self._assert_rejected(self._propose(trigger_price=70000), 'ARM_ALREADY_REACHED')
self._assert_rejected(self._propose(trigger_price=65000), 'ARM_ALREADY_REACHED')
def test_floor_above_trigger_rejected(self):
self._assert_rejected(self._propose(min_sell_price=85000), 'ARM_IMMEDIATE')
def test_budget_rejected(self):
self._assert_rejected(self._propose(qty=None, budget=1_000_000), 'TRAIL_BUDGET')
def test_bad_steps_rejected(self):
self._assert_rejected(self._propose(trail_steps=[{'pct': 20, 'cum': 20},
{'pct': 10, 'cum': 50}]), 'TRAIL_STEPS')
def test_qty_too_small_rejected(self):
# qty=0 은 '미입력'(None)이 아니라 '값 0' 이라 MISSING_QTY 를 지나 계단 배분에서 걸린다.
self._assert_rejected(self._propose(qty=0), 'TRAIL_QTY')
def test_no_qty_rejected(self):
self._assert_rejected(self._propose(qty=None), 'MISSING_QTY')
def test_duplicate_arm_rejected(self):
t.register_arm(account='일반', symbol='005930', symbol_name='삼성전자', qty=10,
trigger_price=90000, steps=t.normalize_steps(STEPS))
self._assert_rejected(self._propose(), 'TRAIL_DUPLICATE')
def test_existing_trailing_blocks_arm(self):
"""같은 계좌·종목에 활성 트레일링이 있으면 예약도 막는다 — 같은 주식을 두 번 팔 수 없다."""
st = t.normalize_steps(STEPS)
lv = t.compute_step_levels(70000, st, None)
qtys = t.allocate_step_qty(100, [s['weight'] for s in st])
legs = [dict(s, qty=q, ord_no=f'O{s["n"]}') for s, q in zip(lv, qtys)]
t.register_steps(account='일반', symbol='005930', symbol_name='삼성전자',
total_qty=100, peak=70000, steps=legs)
self._assert_rejected(self._propose(), 'TRAIL_DUPLICATE')
def test_arm_blocks_plain_trailing_too(self):
"""반대 방향도 막혀야 한다 — 대기 예약이 있으면 일반 트레일링을 새로 걸 수 없다."""
t.register_arm(account='일반', symbol='005930', symbol_name='삼성전자', qty=10,
trigger_price=90000, steps=t.normalize_steps(STEPS))
res = handler.propose_trade(account='일반', side='SELL', symbol='005930',
symbol_name='삼성전자', qty=100,
order_type='TRAILING_STOP', trail_steps=STEPS)
self._assert_rejected(res, 'TRAIL_DUPLICATE')
def test_holiday_now_actually_blocks(self):
"""guards.is_today_holiday 의 dict 미인식 버그를 고친 뒤라야 이게 막힌다."""
self.collect.return_value = dict(_md(), is_holiday=True)
res = self._propose()
self.assertFalse(res['ok'], msg=res.get('message'))
self.assertIn('HOLIDAY', res['message'])
class ArmRegisterTests(unittest.TestCase):
"""PIN 승인 → 대기 예약 등록. 키움 호출이 0건이어야 한다."""
def setUp(self):
self.tmp = Path(tempfile.mkdtemp()) / 'trailing_stops.json'
self._orig = (t.STATE_FILE, t._LOCK_FILE)
t.STATE_FILE = self.tmp
t._LOCK_FILE = self.tmp.with_suffix('.json.lock')
mock.patch.object(handler.ledger, 'append', return_value=None).start()
self.submit = mock.patch.object(handler.kiwoom_order, 'submit').start()
self.addCleanup(mock.patch.stopall)
self.card = mock.MagicMock()
self.card.card_id = 'AB3D'
self.card.payload = {
'account': '일반', 'side': 'SELL', 'symbol': '005930', 'symbol_name': '삼성전자',
'qty': 100, 'price': None, 'order_type': 'TRAILING_ARM', 'routing_suffix': '_AL',
'trigger_price': 80000, 'trail_steps': t.normalize_steps(STEPS),
'trail_preview': [], 'min_sell_price': 60000,
}
def tearDown(self):
t.STATE_FILE, t._LOCK_FILE = self._orig
shutil.rmtree(self.tmp.parent, ignore_errors=True)
def test_register_places_no_order(self):
res = handler._register_trailing_arm(self.card, dry_run=False)
self.assertTrue(res['ok'], msg=res.get('message'))
self.submit.assert_not_called()
arms = t.list_arms()
self.assertEqual(len(arms), 1)
self.assertEqual((arms[0]['trigger_price'], arms[0]['card_id']), (80000, 'AB3D'))
self.assertIn('발동가에 닿으면 PIN 재확인 없이', res['message'])
def test_dry_run_writes_nothing(self):
res = handler._register_trailing_arm(self.card, dry_run=True)
self.assertTrue(res['ok'])
self.assertEqual(t.list_arms(), [])
self.submit.assert_not_called()
def test_cancel_removes_arm(self):
handler._register_trailing_arm(self.card, dry_run=False)
arm_id = t.list_arms()[0]['id']
with mock.patch.object(handler.sidecar, 'guard_or_raise', return_value=None):
res = handler.cancel_trailing_arm(arm_id)
self.assertTrue(res['ok'])
self.assertEqual(t.list_arms(), [])
def test_cancel_warns_about_orphan_orders(self):
"""발동 중 접수된 주문이 있으면 자동 취소하지 않고 주문번호를 알려준다."""
handler._register_trailing_arm(self.card, dry_run=False)
arm_id = t.list_arms()[0]['id']
t.mark_arm_firing(arm_id)
t.record_arm_leg(arm_id, {'n': 1, 'qty': 20, 'cond_uv': 72000, 'ord_uv': 71900,
'ord_no': '0374065'})
with mock.patch.object(handler.sidecar, 'guard_or_raise', return_value=None):
res = handler.cancel_trailing_arm(arm_id)
self.assertTrue(res['ok'])
self.assertIn('0374065', res['message'])
self.assertIn('별도로 취소', res['message'])
if __name__ == '__main__':
import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
unittest.main(verbosity=2)
@@ -19,8 +19,12 @@ import tempfile
import unittest
from pathlib import Path
from datetime import datetime, timedelta, timezone
from orders import trailing as t
KST = timezone(timedelta(hours=9))
class NormalizeStepsTests(unittest.TestCase):
"""입력한 누적 비중 → 내부 추가 비중."""
@@ -358,6 +362,376 @@ console.log(JSON.stringify(out));
f'비중환산 {cums}')
class ArmPlanTests(unittest.TestCase):
"""예약 트레일링 발동 계획 — 순수 함수라 키움 없이 전부 검증된다."""
def setUp(self):
self.steps = t.normalize_steps([{'pct': 10, 'cum': 20}, {'pct': 20, 'cum': 50},
{'pct': 30, 'cum': 100}])
def _arm(self, qty=100, trigger=80000, floor=None):
return {'id': 'ARM-TEST', 'account': '일반', 'symbol': '005930',
'symbol_name': '삼성전자', 'qty': qty, 'trigger_price': trigger,
'min_sell_price': floor, 'steps': self.steps}
def test_reach_is_upward_only(self):
self.assertEqual(t.plan_arm_fire(self._arm(), 79999, 100)['reason'], 'not_reached')
# 발동가 정확히 일치도 발동 — 판정이 >= 라서.
self.assertEqual(t.plan_arm_fire(self._arm(), 80000, 100)['action'], 'fire')
def test_no_price_is_not_a_reach(self):
self.assertEqual(t.plan_arm_fire(self._arm(), 0, 100)['reason'], 'no_price')
def test_peak_is_current_price_not_trigger(self):
"""갭 상승으로 발동가를 훌쩍 넘겨 열리면 고점은 현재가여야 한다."""
r = t.plan_arm_fire(self._arm(trigger=80000), 95000, 100)
self.assertEqual(r['peak'], 95000)
def test_qty_shrinks_to_sellable(self):
r = t.plan_arm_fire(self._arm(qty=100), 80000, 40)
self.assertEqual((r['qty_used'], r['reduced']), (40, True))
self.assertEqual(sum(s['qty'] for s in r['legs']), 40)
def test_no_sellable_drops_the_arm(self):
self.assertEqual(t.plan_arm_fire(self._arm(), 80000, 0)['action'], 'drop')
def test_one_share_keeps_a_step_alive(self):
r = t.plan_arm_fire(self._arm(qty=100), 80000, 1)
self.assertEqual(r['action'], 'fire')
self.assertEqual(sum(s['qty'] for s in r['legs']), 1)
def test_actual_stops_never_worse_than_preview(self):
"""승인 화면은 발동가 기준으로 보여준다 — 실제 손절선이 그보다 낮으면 안 된다."""
preview = t.compute_step_levels(80000, self.steps, None)
for cur in (80000, 82000, 90000, 120000):
legs = t.plan_arm_fire(self._arm(trigger=80000), cur, 100)['legs']
for actual, shown in zip(legs, preview):
self.assertGreaterEqual(actual['cond_uv'], shown['cond_uv'], f'현재가 {cur}')
def test_first_stop_always_below_current(self):
"""발동 즉시 체결되면 트레일링이 아니다 — 무작위 시세로 불변식 확인."""
rnd = random.Random(20260825)
for _ in range(300):
trigger = rnd.randrange(1000, 300000)
cur = trigger + rnd.randrange(0, 50000)
floor = rnd.choice([None, int(trigger * 0.7), int(trigger * 0.95)])
r = t.plan_arm_fire(self._arm(trigger=trigger, floor=floor), cur, 100)
if r['action'] == 'fire':
self.assertLess(r['legs'][0]['cond_uv'], cur,
f'trigger={trigger} cur={cur} floor={floor}')
def test_floor_ties_steps_without_merging(self):
r = t.plan_arm_fire(self._arm(trigger=80000, floor=75000), 80000, 100)
self.assertEqual(len(r['legs']), 3) # 병합하지 않는다
self.assertEqual({s['cond_uv'] for s in r['legs']}, {75000})
class ArmStateTests(unittest.TestCase):
"""대기 예약 상태 — 예약과 한 파일을 공유하므로 서로를 지우지 않는지가 핵심."""
def setUp(self):
self.tmp = Path(tempfile.mkdtemp()) / 'trailing_stops.json'
self._orig = (t.STATE_FILE, t._LOCK_FILE)
t.STATE_FILE = self.tmp
t._LOCK_FILE = self.tmp.with_suffix('.json.lock')
self.steps = t.normalize_steps([{'pct': 10, 'cum': 20}, {'pct': 20, 'cum': 50},
{'pct': 30, 'cum': 100}])
self.arm = t.register_arm(account='일반', symbol='005930', symbol_name='삼성전자',
qty=100, trigger_price=80000, steps=self.steps,
min_sell_price=70000, card_id='AB3D')
def tearDown(self):
t.STATE_FILE, t._LOCK_FILE = self._orig
shutil.rmtree(self.tmp.parent, ignore_errors=True)
def _legs(self, peak=82000, qty=100):
lv = t.compute_step_levels(peak, self.steps, 70000)
qtys = t.allocate_step_qty(qty, [s['weight'] for s in self.steps])
return [dict(s, qty=q, ord_no=f'O{s["n"]:04d}') for s, q in zip(lv, qtys) if q > 0]
def test_arm_stores_definition_only(self):
"""가격·라우팅을 굳혀두면 발동 시점 시세와 어긋난다."""
a = t.get_arm(self.arm['id'])
self.assertNotIn('cond_uv', a['steps'][0])
self.assertNotIn('routing_suffix', a)
self.assertIsNone(a['firing_at'])
def test_reservation_writes_do_not_wipe_arms(self):
"""레거시 6개 호출부가 _write(reservations) 로 arms 를 지우면 안 된다."""
res = t.register_steps(account='ISA', symbol='000660', symbol_name='SK하이닉스',
total_qty=100, peak=500000, steps=self._legs(500000))
for fn in (lambda: t.commit_peak(res['id'], 510000),
lambda: t.commit_step_modify(res['id'], 1, 'X0001', 510000, 460000, 459000),
lambda: t.should_notify_failure(res['id'], 9e9, 1800),
lambda: t.remove_step(res['id'], 3, 'test')):
fn()
self.assertEqual(len(t.list_arms()), 1, '대기 예약이 사라졌다')
def test_firing_marker_blocks_refire(self):
self.assertTrue(t.mark_arm_firing(self.arm['id']))
self.assertFalse(t.mark_arm_firing(self.arm['id']))
def test_promote_is_atomic(self):
legs = self._legs()
out = t.promote_arm(self.arm['id'], 82000, legs, '_AL')
self.assertEqual(t.list_arms(), [])
r = t.get(out['reservation']['id'])
self.assertEqual((r['peak'], r['entry_peak'], r['routing_suffix']), (82000, 82000, '_AL'))
self.assertEqual((r['min_sell_price'], r['card_id']), (70000, 'AB3D'))
self.assertEqual(r['qty'], sum(s['qty'] for s in legs))
def test_promote_unknown_arm_is_noop(self):
self.assertIsNone(t.promote_arm('ARM-NOPE', 82000, self._legs()))
self.assertEqual(len(t.list_arms()), 1)
def test_record_leg_survives_for_crash_diagnosis(self):
t.mark_arm_firing(self.arm['id'])
t.record_arm_leg(self.arm['id'], self._legs()[0])
self.assertEqual(t.get_arm(self.arm['id'])['fired_legs'][0]['ord_no'], 'O0001')
def test_duplicate_lookup_covers_both_kinds(self):
self.assertIsNotNone(t.find_arm_by_symbol('일반', '005930'))
self.assertIsNone(t.find_arm_by_symbol('ISA', '005930'))
def test_remove_arm(self):
self.assertEqual(t.remove_arm(self.arm['id'], 'manual')['removed_reason'], 'manual')
self.assertEqual(t.list_arms(), [])
self.assertIsNone(t.remove_arm(self.arm['id']))
class ArmExpiryTests(unittest.TestCase):
"""대기 예약 수명 = 등록일 당일 (2026-08-25 무기한에서 변경).
거래소가 지워주는 스톱주문과 달리 우리 파일에만 있으므로 우리가 만료를 재야 한다.
"""
def _arm(self, created):
return {'id': 'ARM-T', 'created_at': created, 'trigger_price': 80000}
def test_same_day_during_session_is_alive(self):
for hh, mm in ((8, 0), (9, 0), (9, 0), (12, 30), (15, 25), (19, 59)):
now = datetime(2026, 8, 25, hh, mm, tzinfo=KST)
self.assertFalse(t.is_arm_expired(self._arm('2026-08-25T09:30:00+09:00'), now),
f'{hh:02d}:{mm:02d} 에 만료로 잡혔다')
def test_opening_gap_is_not_expiry(self):
"""09:00:00~09:00:30 은 session_at 이 CLOSED 를 주는 틈 — 여기서 만료되면 안 된다."""
now = datetime(2026, 8, 25, 9, 0, 10, tzinfo=KST)
self.assertFalse(t.is_arm_expired(self._arm('2026-08-25T08:30:00+09:00'), now))
def test_after_session_end_same_day_expires(self):
now = datetime(2026, 8, 25, 20, 0, tzinfo=KST)
self.assertTrue(t.is_arm_expired(self._arm('2026-08-25T09:30:00+09:00'), now))
def test_next_day_expires(self):
now = datetime(2026, 8, 26, 8, 0, tzinfo=KST)
self.assertTrue(t.is_arm_expired(self._arm('2026-08-25T09:30:00+09:00'), now))
def test_missing_created_at_is_kept(self):
"""날짜를 모르면 지우지 않는다 — 조용히 사라지는 쪽이 더 위험하다."""
self.assertFalse(t.is_arm_expired({'id': 'ARM-X', 'trigger_price': 1},
datetime(2026, 8, 26, 9, 0, tzinfo=KST)))
class ArmMonitorTests(unittest.TestCase):
"""감시 루프의 발동 단계 — 실주문 경로라 stub 으로 전부 막고 호출만 관찰한다."""
def setUp(self):
self.tmp = Path(tempfile.mkdtemp()) / 'trailing_stops.json'
self._orig = (t.STATE_FILE, t._LOCK_FILE)
t.STATE_FILE = self.tmp
t._LOCK_FILE = self.tmp.with_suffix('.json.lock')
import trailing_monitor as tm
self.tm = tm
self.sent, self.submits = [], []
self.state = {'quote': 70000, 'able': 100, 'disabled': False}
self._orig_fns = (tm.kc.get_open_orders, tm.kc.get_watchlist_quotes, tm.kc.get_positions,
tm.handler.place_trailing_legs, tm.send_telegram,
tm.sidecar.is_disabled, tm.ledger.append,
tm.datasource.nxt_eligible, tm.guards.determine_routing)
tm.kc.get_open_orders = lambda acct, side=None: []
tm.kc.get_watchlist_quotes = lambda codes, exchange='AL': {
c: {'price': self.state['quote']} for c in codes}
tm.kc.get_positions = lambda acct, exchange='KRX': [
{'code': '005930', 'qty': self.state['able'], 'trde_able_qty': self.state['able']}]
tm.handler.place_trailing_legs = self._fake_place
tm.send_telegram = lambda msg, parse_mode=None: self.sent.append(msg)
tm.sidecar.is_disabled = lambda: self.state['disabled']
tm.ledger.append = lambda ev, payload: None
tm.datasource.nxt_eligible = lambda code: True
tm.guards.determine_routing = lambda now, nxt, force: '_AL'
self.steps = t.normalize_steps([{'pct': 10, 'cum': 20}, {'pct': 20, 'cum': 50},
{'pct': 30, 'cum': 100}])
self.arm = t.register_arm(account='일반', symbol='005930', symbol_name='삼성전자',
qty=100, trigger_price=80000, steps=self.steps,
card_id='AB3D')
def tearDown(self):
(self.tm.kc.get_open_orders, self.tm.kc.get_watchlist_quotes, self.tm.kc.get_positions,
self.tm.handler.place_trailing_legs, self.tm.send_telegram,
self.tm.sidecar.is_disabled, self.tm.ledger.append,
self.tm.datasource.nxt_eligible, self.tm.guards.determine_routing) = self._orig_fns
t.STATE_FILE, t._LOCK_FILE = self._orig
shutil.rmtree(self.tmp.parent, ignore_errors=True)
def _fake_place(self, account, symbol, steps, routing_suffix, card_id, dry_run,
on_placed=None):
placed = []
for s in steps:
self.submits.append({'n': s['n'], 'qty': s['qty'], 'cond': s['cond_uv'],
'routing': routing_suffix})
leg = dict(s, ord_no=f'O{s["n"]:04d}')
placed.append(leg)
if on_placed:
on_placed(leg)
return placed, [], []
def test_below_trigger_places_nothing(self):
self.tm.check(force=True)
self.assertEqual(self.submits, [])
self.assertEqual(len(t.list_arms()), 1)
def test_reach_fires_and_promotes(self):
self.state['quote'] = 81000
self.tm.check(force=True)
self.assertEqual(len(self.submits), 3)
self.assertEqual([s['routing'] for s in self.submits], ['_AL'] * 3)
self.assertEqual(t.list_arms(), [])
self.assertEqual(len(t.list_active()), 1)
self.assertEqual(t.list_active()[0]['peak'], 81000) # 고점 = 발동 시점 현재가
self.assertIn('발동', self.sent[0])
def test_sidecar_blocks_before_marking(self):
"""마킹 후 막히면 예약이 갇힌다 — 사이드카는 마킹 전에 봐야 한다."""
self.state['quote'] = 81000
self.state['disabled'] = True
self.tm.check(force=True)
self.assertEqual(self.submits, [])
self.assertIsNone(t.get_arm(self.arm['id'])['firing_at'])
self.assertEqual(len(t.list_arms()), 1)
def test_stuck_arm_is_not_refired(self):
t.mark_arm_firing(self.arm['id'])
self.state['quote'] = 81000
self.tm.check(force=True)
self.assertEqual(self.submits, [], '발동 처리 중단 예약을 재발주했다')
self.assertEqual(len(t.list_arms()), 1)
def _age_arm(self, days=1):
"""등록일을 과거로 밀어 만료 상태를 만든다."""
past = (datetime.now(KST) - timedelta(days=days)).isoformat(timespec='seconds')
arms = t.list_arms()
arms[0]['created_at'] = past
with t._FileLock(t._LOCK_FILE):
t._write(arms=arms)
def test_expired_arm_is_purged_not_fired(self):
"""어제 예약이 오늘 시세로 발동하면 안 된다 — 지우고 알린다."""
self._age_arm()
self.state['quote'] = 81000 # 발동가 도달 상태로 만들어도
self.tm.check(force=True)
self.assertEqual(self.submits, [], '만료 예약을 발주했다')
self.assertEqual(t.list_arms(), [])
self.assertEqual(len(t.list_active()), 0)
self.assertTrue(any('만료' in m for m in self.sent), self.sent)
def test_expiry_purge_survives_quote_failure(self):
"""시세 조회가 실패해도 만료 정리는 돼야 한다 (정리가 시세보다 앞이라서)."""
self._age_arm()
self.tm.kc.get_watchlist_quotes = lambda codes, exchange='AL': (
_ for _ in ()).throw(RuntimeError('시세 실패'))
self.tm.check(force=True)
self.assertEqual(t.list_arms(), [])
def test_expiry_notice_is_batched(self):
"""여러 건이 같이 만료돼도 텔레그램은 한 통."""
t.register_arm(account='일반', symbol='000660', symbol_name='SK하이닉스', qty=10,
trigger_price=500000, steps=self.steps)
past = (datetime.now(KST) - timedelta(days=3)).isoformat(timespec='seconds')
arms = t.list_arms()
for a in arms:
a['created_at'] = past
with t._FileLock(t._LOCK_FILE):
t._write(arms=arms)
self.tm.check(force=True)
self.assertEqual(t.list_arms(), [])
self.assertEqual(len([m for m in self.sent if '만료' in m]), 1, self.sent)
def test_dry_run_does_not_purge(self):
self._age_arm()
self.tm.check(force=True, dry_run=True)
self.assertEqual(len(t.list_arms()), 1)
self.assertEqual(self.sent, [])
def test_open_order_query_failure_holds_the_arm(self):
"""미체결 매도 수량을 모르면 발주하지 않는다 — 0 으로 단정하면 과다 매도가 된다."""
self.state['quote'] = 81000
self.tm.kc.get_open_orders = lambda acct, side=None: (_ for _ in ()).throw(
RuntimeError('조회 실패'))
self.tm.check(force=True)
self.assertEqual(self.submits, [])
self.assertEqual(len(t.list_arms()), 1)
self.assertIsNone(t.get_arm(self.arm['id'])['firing_at'])
def test_pending_sell_reduces_sellable(self):
"""같은 종목에 미체결 매도가 있으면 그만큼 빼고 발주한다."""
self.state['quote'] = 81000
self.tm.kc.get_open_orders = lambda acct, side=None: [
{'code': '005930', 'side': 'SELL', 'unfilled_qty': 60, 'ord_no': 'X1',
'routing_suffix': ''}]
self.tm.check(force=True)
self.assertEqual(sum(s['qty'] for s in self.submits), 40)
def test_position_query_failure_holds_instead_of_dropping(self):
self.state['quote'] = 81000
self.tm.kc.get_positions = lambda acct, exchange='KRX': (_ for _ in ()).throw(
RuntimeError('조회 실패'))
self.tm.check(force=True)
self.assertEqual(self.submits, [])
self.assertEqual(len(t.list_arms()), 1, '조회 실패로 예약을 지웠다')
def test_sold_out_position_drops_the_arm(self):
self.state['quote'] = 81000
self.state['able'] = 0
self.tm.check(force=True)
self.assertEqual(self.submits, [])
self.assertEqual(t.list_arms(), [])
self.assertIn('삭제', self.sent[0])
def test_partial_holding_shrinks_the_order(self):
self.state['quote'] = 81000
self.state['able'] = 40
self.tm.check(force=True)
self.assertEqual(sum(s['qty'] for s in self.submits), 40)
self.assertIn('줄여', self.sent[0])
def test_arm_failure_does_not_break_stop_raising(self):
"""arm 코드가 터져도 실주문을 지키는 손절선 상향은 완료돼야 한다."""
lv = t.compute_step_levels(5500, self.steps, None)
qtys = t.allocate_step_qty(300, [s['weight'] for s in self.steps])
legs = [dict(s, qty=q, ord_no=f'A{s["n"]:04d}') for s, q in zip(lv, qtys)]
res = t.register_steps(account='일반', symbol='381620', symbol_name='제닉스로보틱스',
total_qty=300, peak=5500, steps=legs)
self.tm.kc.get_open_orders = lambda acct, side=None: [
{'ord_no': s['ord_no'], 'unfilled_qty': s['qty'], 'routing_suffix': ''} for s in legs]
modified = []
self.tm.kiwoom_order.modify_order = lambda **kw: (
modified.append(kw['orig_ord_no']) or {'ok': True, 'new_ord_no': 'N1'})
self.state['quote'] = 81000 # 두 종목 모두 고점 갱신 + arm 도달
self.tm.handler.place_trailing_legs = lambda **kw: (_ for _ in ()).throw(
RuntimeError('arm 폭발'))
try:
rc = self.tm.check(force=True)
finally:
self.tm.kiwoom_order.modify_order = self._orig_fns[3] and __import__(
'orders.kiwoom_order', fromlist=['modify_order']).modify_order
self.assertEqual(rc, 0)
self.assertEqual(len(modified), 3, '손절선 상향이 arm 예외에 휩쓸렸다')
self.assertEqual(t.get(res['id'])['peak'], 81000)
if __name__ == '__main__':
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
unittest.main(verbosity=2)
+339 -23
View File
@@ -22,6 +22,30 @@
상태는 파일(state/trailing_stops.json)에 저장 — 웹·감시 루프가 각각 다른 프로세스라
같은 예약 목록을 봐야 한다. 동시성은 fcntl flock 으로 직렬화 (pin.py 와 같은 패턴).
── 대기 예약 (arms) — 예약 트레일링, 2026-08-25 ──────────────────────────────
같은 파일에 `arms` 키가 하나 더 있다. **발동가에 닿으면 그때 트레일링을 시작하는 예약**으로,
`reservations`(이미 키움에 주문이 나간 것)와 성질이 정반대다:
reservations : 주문이 키움에 있음 → 감시 루프가 죽어도 손절선은 살아있음, 장 마감에 소멸
arms : 주문이 없고 이 파일에만 있음 → 감시가 죽으면 발동 안 함, **당일만 유효**
⚠️ **arm 도 스톱주문과 같은 수명(등록일 당일)이다** — 2026-08-25 관리자님 결정으로 무기한에서
바뀌었다. 거래소가 지워주는 스톱주문과 달리 arm 은 우리 파일에만 있으니 우리가 지운다:
`is_arm_expired` 판정 + 감시 루프의 다음 거래일 첫 사이클 정리.
⚠️ **두 갈래를 한 파일에 둔 이유는 발동이 "arm 제거 + 예약 등록"이라는 하나의 원자적
전이여야 해서다.** promote_arm 이 락 하나 안에서 os.replace 1회로 끝낸다. 파일을 쪼개면
락을 중첩해야 하고(데드락), 중간에 죽으면 둘 다 존재하거나 둘 다 사라진다.
⚠️ 그 대가로 **_write 는 None 인 쪽을 디스크에서 다시 읽어 보존**한다. 이 계약이 깨지면
reservations 쪽 쓰기 6곳이 arms 를 조용히 전멸시킨다 (테스트로 고정해 둠).
⚠️ arm 의 steps 에는 **가격·주수를 저장하지 않는다**(정의 n/pct/cum/weight 만). routing_suffix
도 저장하지 않는다. 갭 상승으로 발동가를 훌쩍 넘겨 열릴 수 있고, 무기한 대기라 등록 시점
세션(KRX)과 발동 시점 세션(NXT)이 달라 옛 suffix 로 발주하면 거부된다. 둘 다 발동 시점에
plan_arm_fire / guards.determine_routing 으로 새로 계산한다.
"""
from __future__ import annotations
@@ -34,7 +58,7 @@ from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Optional
from .guards import tick_size
from .guards import after_trading_day, tick_size
WORKSPACE_ROOT = Path(__file__).resolve().parent.parent.parent
STATE_FILE = WORKSPACE_ROOT / 'state' / 'trailing_stops.json'
@@ -58,10 +82,18 @@ ORD_UV_GAP_TICKS = 2
MAX_STEPS = 5
# 혼동되는 글자(0/O, 1/I/l)를 뺀 영숫자 — 사람이 눈으로 읽고 옮겨 적는 id 라서.
_ID_CHARS = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
def _now_iso() -> str:
return datetime.now(KST).isoformat(timespec='seconds')
def _new_id(prefix: str) -> str:
return prefix + ''.join(secrets.choice(_ID_CHARS) for _ in range(4))
def floor_to_tick(price: int) -> int:
"""호가단위로 내림. 매도 조건단가·지정가는 내림이 안전(더 빨리 발동/체결)."""
t = tick_size(price)
@@ -213,21 +245,48 @@ class _FileLock:
_LOCK_FILE = STATE_FILE.with_suffix(STATE_FILE.suffix + '.lock')
def _read() -> list:
def _read_doc() -> dict:
"""상태파일 전체. `reservations`(키움에 주문이 나가 있는 예약) 와
`arms`(발동가 도달을 기다리는 대기 예약) 두 갈래가 한 파일에 들어 있다.
한 파일에 둔 이유는 발동이 "arm 제거 + 예약 등록" 이라는 하나의 원자적 전이이기
때문이다. 파일이 둘이면 락을 중첩해야 하고, 중간에 죽으면 둘 다 존재하거나 둘 다
사라진다.
"""
if not STATE_FILE.exists():
return []
return {'reservations': [], 'arms': []}
try:
data = json.loads(STATE_FILE.read_text(encoding='utf-8'))
except (OSError, ValueError):
return []
return data.get('reservations') or []
return {'reservations': [], 'arms': []}
if not isinstance(data, dict):
return {'reservations': [], 'arms': []}
return {'reservations': data.get('reservations') or [],
'arms': data.get('arms') or []}
def _write(reservations: list) -> None:
def _read() -> list:
return _read_doc()['reservations']
def _read_arms() -> list:
return _read_doc()['arms']
def _write(reservations: Optional[list] = None, arms: Optional[list] = None) -> None:
"""None 인 쪽은 디스크의 현재 값을 유지한다.
⚠️ 두 갈래가 한 파일에 있으므로 한쪽만 쓰는 호출이 다른 쪽을 지우면 안 된다.
반드시 _FileLock 안에서 부를 것 — 여기서 다시 읽어 병합한다.
"""
doc = _read_doc()
if reservations is not None:
doc['reservations'] = reservations
if arms is not None:
doc['arms'] = arms
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
tmp = STATE_FILE.with_suffix(STATE_FILE.suffix + '.tmp')
tmp.write_text(json.dumps({'reservations': reservations}, ensure_ascii=False, indent=2),
encoding='utf-8')
tmp.write_text(json.dumps(doc, ensure_ascii=False, indent=2), encoding='utf-8')
os.replace(tmp, STATE_FILE)
@@ -251,28 +310,20 @@ def find_by_symbol(account: str, symbol: str) -> Optional[dict]:
return None
def register_steps(account: str, symbol: str, symbol_name: str, total_qty: int,
peak: int, steps: list, routing_suffix: str = '',
card_id: Optional[str] = None,
min_sell_price: Optional[int] = None) -> dict:
"""키움에 계단별 스톱주문이 접수된 직후 호출 — 레그 목록을 예약 1건으로 등록.
def _build_reservation(account: str, symbol: str, symbol_name: str, total_qty: int,
peak: int, steps: list, routing_suffix: str,
card_id: Optional[str], min_sell_price: Optional[int]) -> dict:
"""접수된 레그 목록 → 예약 dict (파일 접근 없는 순수 조립).
steps 각 항목은 접수 결과까지 채워져 있어야 한다:
{'n', 'pct', 'cum', 'weight', 'qty', 'ord_no', 'cond_uv', 'ord_uv'}
접수에 실패한 레그는 호출측이 빼고 넘긴다 — 여기 등록된 것은 전부 키움에 살아있는
주문이어야 감시 루프의 생존 판정이 성립한다.
초기 고점(peak)은 등록 시점 현재가다. 과거 고점(52주·매수후) 기준 옵션은 손절선이
현재가 위로 올라가 즉시 발동하는 구조라 2026-07-30 제거됐다.
register_steps(등록 즉시 트레일링)와 promote_arm(대기 예약 발동)이 공유한다.
"""
if not steps:
raise ValueError('등록할 계단이 없음 — 접수 성공한 레그가 하나도 없다')
for s in steps:
if not s.get('ord_no'):
raise ValueError(f'{s.get("n")}단계 ord_no 없음 — 접수 확인된 주문만 등록')
res = {
'id': 'TRL-' + ''.join(secrets.choice('ABCDEFGHJKLMNPQRSTUVWXYZ23456789') for _ in range(4)),
return {
'id': _new_id('TRL-'),
'account': account,
'symbol': symbol,
'symbol_name': symbol_name,
@@ -289,6 +340,25 @@ def register_steps(account: str, symbol: str, symbol_name: str, total_qty: int,
'updated_at': _now_iso(),
'entry_peak': peak,
}
def register_steps(account: str, symbol: str, symbol_name: str, total_qty: int,
peak: int, steps: list, routing_suffix: str = '',
card_id: Optional[str] = None,
min_sell_price: Optional[int] = None) -> dict:
"""키움에 계단별 스톱주문이 접수된 직후 호출 — 레그 목록을 예약 1건으로 등록.
steps 각 항목은 접수 결과까지 채워져 있어야 한다:
{'n', 'pct', 'cum', 'weight', 'qty', 'ord_no', 'cond_uv', 'ord_uv'}
접수에 실패한 레그는 호출측이 빼고 넘긴다 — 여기 등록된 것은 전부 키움에 살아있는
주문이어야 감시 루프의 생존 판정이 성립한다.
초기 고점(peak)은 등록 시점 현재가다. 과거 고점(52주·매수후) 기준 옵션은 손절선이
현재가 위로 올라가 즉시 발동하는 구조라 2026-07-30 제거됐다.
"""
res = _build_reservation(account, symbol, symbol_name, total_qty, peak, steps,
routing_suffix, card_id, min_sell_price)
with _FileLock(_LOCK_FILE):
reservations = _read()
reservations.append(res)
@@ -399,3 +469,249 @@ def remove(res_id: str, reason: str = '') -> Optional[dict]:
gone['removed_reason'] = reason
return gone
return None
# ---------------------------------------------------------------------------
# 대기 예약 (arm) — 발동가에 닿으면 그때 트레일링을 등록한다
#
# 트레일링 스톱은 등록 즉시 계단별 스톱주문이 키움에 나가고 그 순간의 현재가가 고점이
# 된다. 아직 오르지 않은 종목엔 그게 불리해서, 발동가를 미리 정해두고 현재가가 거기에
# 올라와 닿는 순간 그때의 현재가를 고점으로 트레일링을 시작하는 갈래를 둔다.
#
# 대기 중인 예약은 키움이 아니라 이 파일에만 있다 — 거래소가 지워주지 않으므로 **우리가**
# 스톱주문과 같은 수명(등록일 당일)을 강제한다(is_arm_expired). 2026-08-25 관리자님 결정.
# 대신 감시 프로세스가 죽어 있으면 발동하지 않는다 — 이건 스톱주문과 정반대 성질이다.
# ---------------------------------------------------------------------------
def list_arms() -> list:
"""대기 예약 전체 (락 없이 읽기 — 감시 루프의 스냅샷용)."""
return _read_arms()
def get_arm(arm_id: str) -> Optional[dict]:
for a in _read_arms():
if a['id'] == arm_id:
return a
return None
def find_arm_by_symbol(account: str, symbol: str) -> Optional[dict]:
"""같은 계좌·종목의 대기 예약. 중복 등록 차단용."""
for a in _read_arms():
if a['account'] == account and a['symbol'] == symbol:
return a
return None
def register_arm(account: str, symbol: str, symbol_name: str, qty: int,
trigger_price: int, steps: list,
min_sell_price: Optional[int] = None,
card_id: Optional[str] = None) -> dict:
"""PIN 승인 직후 호출 — 발동가 도달을 기다리는 예약 1건 등록. 키움 주문은 아직 없다.
steps 는 normalize_steps 결과, 즉 **정의만**(n/pct/cum/weight)이다. 조건단가·지정가·
계단별 주수는 저장하지 않는다 — 갭 상승으로 발동가를 훌쩍 넘겨 열릴 수 있고 그 사이
보유수량이 줄었을 수도 있어서, 발동 시점의 현재가와 매도가능 수량으로 다시 계산해야
실제 시세와 맞는다.
routing_suffix 도 저장하지 않는다. 당일 안에서도 등록 시점 세션(정규장)과 발동 시점
세션(NXT 애프터)이 다를 수 있고, 옛 suffix 로 발주하면 거부된다.
"""
if not steps:
raise ValueError('계단이 비어 있음')
if trigger_price <= 0:
raise ValueError('발동가가 0 이하')
arm = {
'id': _new_id('ARM-'),
'account': account,
'symbol': symbol,
'symbol_name': symbol_name,
'qty': qty,
'trigger_price': trigger_price,
'min_sell_price': min_sell_price,
'steps': [{'n': s['n'], 'pct': s['pct'], 'cum': s['cum'], 'weight': s['weight']}
for s in steps],
'card_id': card_id,
'created_at': _now_iso(),
'updated_at': _now_iso(),
'firing_at': None,
}
with _FileLock(_LOCK_FILE):
arms = _read_arms()
arms.append(arm)
_write(arms=arms)
return arm
def remove_arm(arm_id: str, reason: str = '') -> Optional[dict]:
"""대기 예약 제거 (취소·수량 소멸·계단 소멸). 반환값은 제거된 arm — 알림 메시지용."""
with _FileLock(_LOCK_FILE):
arms = _read_arms()
for i, a in enumerate(arms):
if a['id'] == arm_id:
gone = arms.pop(i)
_write(arms=arms)
gone['removed_reason'] = reason
return gone
return None
def mark_arm_firing(arm_id: str) -> bool:
"""발주 직전 마킹. 이미 마킹돼 있으면 False (재발동 차단).
키움 발주가 성공한 뒤 promote_arm 전에 프로세스가 죽으면 다음 사이클이 같은 종목을
또 판다. 창은 1초 남짓이지만 결과가 이중 매도라, 발주를 시작했다는 사실을 먼저
디스크에 남긴다.
⚠️ 마킹이 남아있는 arm 은 **자동으로 재시도하지 않는다** — 주문이 나갔는지 알 수 없는
상태에서 다시 내는 게 바로 이중 매도다. 감시 루프는 알림만 보내고, 관리자님이 실제
미체결을 확인한 뒤 자산웹에서 취소한다.
"""
with _FileLock(_LOCK_FILE):
arms = _read_arms()
for a in arms:
if a['id'] != arm_id:
continue
if a.get('firing_at'):
return False
a['firing_at'] = _now_iso()
a['updated_at'] = _now_iso()
_write(arms=arms)
return True
return False
def is_arm_expired(arm: dict, now: Optional[datetime] = None) -> bool:
"""등록일의 거래 세션이 끝났으면 만료 (2026-08-25 관리자님 결정으로 무기한 → 당일).
트레일링 스톱주문이 장 마감에 소멸하는 것과 **같은 수명**을 갖게 한 것이다. 대기 예약은
키움이 아니라 우리 파일에만 있어 거래소가 지워주지 않으므로, 우리가 지워야 한다.
판정은 두 갈래:
· 등록일이 오늘보다 이전 → 만료 (밤새 넘긴 것·주말 넘긴 것)
· 등록일이 오늘이고 이미 20:00 지남 → 만료
⚠️ 감시 루프는 08:00~19:59 에만 도므로 **20:00 직후에 지워지지는 않는다.** 실제 정리는
다음 거래일 첫 사이클이다. 그 사이(장외)에 자산웹을 열면 만료 상태로 표시된다 —
화면이 거짓말하지 않게 웹도 같은 판정을 쓴다.
"""
now = now or datetime.now(KST)
created = (arm.get('created_at') or '')[:10]
if not created:
return False # 날짜를 모르면 함부로 지우지 않는다
today = now.strftime('%Y-%m-%d')
if created < today:
return True
return created == today and after_trading_day(now)
def record_arm_leg(arm_id: str, leg: dict) -> bool:
"""발동 중 레그 하나가 접수될 때마다 즉시 기록 (write-ahead).
firing_at 만으로는 중간에 죽었을 때 "몇 단이 실제로 나갔는지" 를 알 수 없다. 키움엔 우리
주문을 식별하는 client order id 가 없어 미체결 목록만으로는 관리자님이 직접 낸 스톱주문과
구분되지 않는다 — 그래서 주문번호를 우리가 적어둔다.
"""
with _FileLock(_LOCK_FILE):
arms = _read_arms()
for a in arms:
if a['id'] != arm_id:
continue
a.setdefault('fired_legs', []).append(
{'n': leg['n'], 'qty': leg['qty'], 'cond_uv': leg['cond_uv'],
'ord_uv': leg['ord_uv'], 'ord_no': leg.get('ord_no', '')})
a['updated_at'] = _now_iso()
_write(arms=arms)
return True
return False
def plan_arm_fire(arm: dict, cur_price: int, sellable_qty: int) -> dict:
"""대기 예약 + 현재가 + 매도가능 수량 → 발주 계획 (순수 함수 — 단위테스트 대상).
반환 {'action', 'reason', 'peak', 'qty_used', 'reduced', 'legs'}
fire : legs 를 그대로 발주한다
skip : 아직 아니다 (미도달·시세 없음) — 아무것도 하지 않는다
drop : 발동할 수 없다 (매도가능 0주·수량 부족) → 예약 삭제 + 알림
고점(peak)은 발동 시점 현재가다. 발동가가 아니라 현재가를 쓰는 이유는 갭 상승으로 발동가를
훌쩍 넘겨 열릴 수 있어서다 — 현재가 >= 발동가 이므로 손절선은 승인 시점 미리보기보다 항상
같거나 높다(관리자님에게 불리해지지 않는 방향).
수량은 min(예약 수량, 매도가능)이다. 대기 중 관리자님이 직접 팔았으면 그만큼 줄여 재배분한다
— 대기 예약이 보유분을 잠그지 않기로 한 결정의 뒷수습이 여기다.
"""
def _out(action, reason):
return {'action': action, 'reason': reason, 'peak': cur_price,
'qty_used': 0, 'reduced': False, 'legs': []}
if cur_price <= 0:
return _out('skip', 'no_price')
if cur_price < arm['trigger_price']:
return _out('skip', 'not_reached')
if sellable_qty <= 0:
return _out('drop', 'no_sellable')
qty_used = min(arm['qty'], sellable_qty)
steps = arm.get('steps') or []
leveled = compute_step_levels(cur_price, steps, arm.get('min_sell_price'))
qtys = allocate_step_qty(qty_used, [s['weight'] for s in steps])
# 0주가 된 계단은 발주할 수 없으니 떨어낸다. 단계 번호(n)는 유지해 몇 단계가 빠졌는지 보인다.
legs = [dict(s, qty=q) for s, q in zip(leveled, qtys) if q > 0]
if not legs:
return _out('drop', 'qty_too_small')
# 실주문 직전 마지막 안전망. propose 가 발동가 기준으로 이미 걸렀고 현재가 >= 발동가,
# 최저 매도가는 승인 후 불변이라 정상 경로에선 도달하지 않는다. 그래도 남기는 이유는
# 여기를 지나면 되돌릴 수 없는 매도 주문이 나가기 때문이다.
if legs[0]['cond_uv'] >= cur_price:
return _out('skip', 'immediate')
return {'action': 'fire', 'reason': '', 'peak': cur_price, 'qty_used': qty_used,
'reduced': qty_used < arm['qty'], 'legs': legs}
def promote_arm(arm_id: str, peak: int, steps: list,
routing_suffix: str = '') -> Optional[dict]:
"""발동 완료 — 대기 예약을 지우고 같은 내용의 트레일링 예약을 등록 (한 락 안에서 원자적).
steps 는 접수 결과까지 채워진 레그 목록(register_steps 와 같은 형식).
둘을 따로 하면 중간에 죽었을 때 arm 과 예약이 동시에 존재하거나 둘 다 사라진다.
반환 {'arm': 제거된 arm, 'reservation': 새 예약} — 알림 메시지 조립용.
"""
with _FileLock(_LOCK_FILE):
doc = _read_doc()
arms = doc['arms']
for i, a in enumerate(arms):
if a['id'] != arm_id:
continue
res = _build_reservation(
a['account'], a['symbol'], a['symbol_name'],
sum(s['qty'] for s in steps), peak, steps,
routing_suffix, a.get('card_id'), a.get('min_sell_price'))
arms.pop(i)
reservations = doc['reservations']
reservations.append(res)
_write(reservations=reservations, arms=arms)
return {'arm': a, 'reservation': res}
return None
def should_notify_arm_failure(arm_id: str, now_ts: float, cooldown_sec: int) -> bool:
"""발동 실패 알림을 보낼지. 보낸다고 판단하면 그 시점을 기록한다 (락 안에서 원자적).
firing_at 이 걸린 채 남은 arm 이나 사이드카 ON 상태는 매 사이클 같은 판정이 반복된다 —
쿨다운 없이 알리면 19:59까지 매분 텔레그램이 쏟아진다. should_notify_failure 와 같은 패턴.
"""
with _FileLock(_LOCK_FILE):
arms = _read_arms()
for a in arms:
if a['id'] != arm_id:
continue
last = a.get('last_fail_notice_ts') or 0
if now_ts - last < cooldown_sec:
return False
a['last_fail_notice_ts'] = now_ts
_write(arms=arms)
return True
return False
@@ -4,14 +4,26 @@
**예약 1건 = 계단(레그) N개.** 고점 대비 하락률이 깊어질수록 더 많이 파는 계단식 청산이라
계단마다 스톱주문이 따로 걸려 있다. 고점이 오르면 살아있는 레그를 전부 상향 정정한다.
LLM을 깨우지 않는다. 하는 일은 딱 두 가지:
LLM을 깨우지 않는다. 하는 일은 세 가지:
1. 레그가 아직 미체결로 살아있는지 확인 (ka10075) — 사라졌으면 정리+알림
2. 현재가가 저장된 고점을 넘었으면 kt10002 정정으로 계단별 조건단가·지정가를 함께 올림
3. **예약 트레일링(TRAILING_ARM) 발동** — 현재가가 발동가에 닿으면 그 시점 현재가를
고점으로 계단별 스톱주문을 발주하고, 대기 예약을 트레일링 예약으로 승격 (2026-08-25)
⚠️ 3번이 **신규 발주 경로**다 (2026-08-25 관리자님 명시 승인). 그전까지 이 감시가 허용된
근거는 "modify_order 하나만 부른다" 였는데 이제는 다르다. 바뀐 근거는 **계좌·종목·수량·
계단·발동가가 PIN 승인 시점에 전부 확정되고, 감시는 발동가 도달 판정만 한다** 는 것이다.
수량 증가·방향 변경 경로는 여전히 없다 (매도가능 부족 시 **축소** 재배분만).
신규 발주·수량 변경·방향 변경은 하지 않는다. 사람이 PIN으로 승인한 주문의
조건단가를 올리는 것뿐이고, 상향 전용이라 손절선이 내려가는 일은 없다.
한 계단이 체결돼도 남은 계단을 재배치하지 않는다 — 재배치엔 취소+신규 발주가 필요해
감시 루프에 자동 발주 경로가 생긴다(2026-07-31 관리자님 결정).
감시 루프에 (승인 범위를 넘는) 자동 발주 경로가 생긴다(2026-07-31 관리자님 결정).
⚠️ arm 단계는 손절선 상향이 **끝난 뒤** 별도 try/except 로 돈다. 순서가 설계다 — 상향은
이미 나간 주문을 지키는 동작이라 arm 쪽 어떤 예외도 그걸 선점해선 안 된다.
⚠️ 발동은 2단계 마킹으로 보호한다. 발주 전에 firing_at 을 디스크에 남기고 레그마다
주문번호를 적는다. **firing_at 이 남은 예약은 자동 재발동하지 않는다** — 주문이 나갔는지
모르는 상태에서 다시 내는 게 곧 이중 매도다. 알림만 보내고 사람이 확인해 취소한다.
⚠️ 레그가 사라진 사유는 **주문번호 단위**인 kt00007 로 판정한다. 종목 단위 집계인
당일매매일지(ka10170)를 쓰면 한 계단이 체결된 날 나머지 계단이 장 마감으로 소멸했을 때
@@ -60,7 +72,8 @@ WORKSPACE = Path('/Users/snowoyh/.openclaw/agents/stock/workspace')
sys.path.insert(0, str(WORKSPACE / 'scripts'))
import kiwoom_client as kc # noqa: E402
from orders import guards, kiwoom_order, trailing # noqa: E402
from orders import card, datasource, guards, kiwoom_order, ledger, sidecar, trailing # noqa: E402
from orders import handler # noqa: E402
from orders.handler import send_telegram # noqa: E402
# 정정 실패 알림 쿨다운 — NXT 시간대에 KRX/SOR 원주문 정정이 거부되면 매 사이클 반복된다.
@@ -180,27 +193,240 @@ def _notify_modify_failed(res: dict, step: dict, reason: str) -> None:
parse_mode=None)
def _sellable_qty(account: str, symbol: str, open_rows) -> tuple:
"""(매도가능 수량, 조회성공). 조회 실패를 0 과 반드시 구분한다 — 눈감고 발주하면 안 되고,
반대로 실패를 0 으로 읽으면 살아있는 예약을 지운다.
trde_able_qty 가 미체결 매도를 이미 뺐는지 확실하지 않아 min 으로 이중차감을 피한다.
"""
try:
positions = kc.get_positions(account)
except Exception as e:
_log(f'보유 조회 실패 [{account}]: {e!r}')
return 0, False
pos = next((p for p in positions if p.get('code') == symbol), None)
if pos is None:
return 0, True
hold = pos.get('qty') or 0
able = pos.get('trde_able_qty') or 0
pending = sum((o.get('unfilled_qty') or 0) for o in (open_rows or [])
if o.get('code') == symbol and o.get('side') == 'SELL')
return max(0, min(able, hold - pending)), True
def _notify_arm_issue(arm: dict, text: str) -> None:
"""대기 예약 관련 경고 — 예약 단위 쿨다운(매 사이클 반복되는 사유가 많다)."""
if not trailing.should_notify_arm_failure(arm['id'], time.time(), FAIL_NOTICE_COOLDOWN_SEC):
_log(f' 알림 생략 (쿨다운 {FAIL_NOTICE_COOLDOWN_SEC}초 내 이미 발송)')
return
try:
send_telegram(
f'⚠️ 예약 트레일링 — {arm["symbol_name"]} ({arm["symbol"]})\n'
f'계좌: {arm["account"]} · 발동가 {arm["trigger_price"]:,}원 · {arm["qty"]:,}주\n'
f'{text}',
parse_mode=None)
except Exception as e:
_log(f'알림 실패: {e!r}')
def _fire_arm(arm: dict, plan: dict, sess: str, dry_run: bool) -> None:
"""발동 — 계단 발주 후 대기 예약을 트레일링 예약으로 승격. 여기부터 되돌릴 수 없다."""
symbol = arm['symbol']
# 라우팅은 등록 시점이 아니라 지금 정한다 — 같은 날 안에서도 정규장에 등록해 NXT
# 애프터에 발동할 수 있고, 옛 suffix 로 발주하면 거부된다.
try:
suffix = guards.determine_routing(datetime.now(KST), datasource.nxt_eligible(symbol), None)
except ValueError as e:
_log(f'라우팅 결정 실패 {arm["id"]} (세션 {sess}): {e!r}')
return
if dry_run:
_log(f'[dry-run] 발동 {arm["id"]} {arm["symbol_name"]} '
f'발동가 {arm["trigger_price"]:,} ≤ 현재가 {plan["peak"]:,} · '
f'{plan["qty_used"]:,}주 · routing {suffix or "KRX"}')
for s in plan['legs']:
_log(f' {s["n"]}단계 −{s["pct"]}% 조건 {s["cond_uv"]:,} / 지정 {s["ord_uv"]:,} '
f'· {s["qty"]:,}주')
return
# write-ahead: 발주를 시작했다는 사실을 먼저 디스크에 남긴다. 이 마킹이 있으면 다음
# 사이클은 재발동하지 않는다 — 주문이 나갔는지 모르는 상태에서 다시 내는 게 이중 매도다.
if not trailing.mark_arm_firing(arm['id']):
_log(f'발동 마킹 실패 {arm["id"]} — 이미 발주 시도된 예약 (재발동 안 함)')
return
placed, failed, _details = handler.place_trailing_legs(
account=arm['account'], symbol=symbol, steps=plan['legs'],
routing_suffix=suffix, card_id=arm.get('card_id'), dry_run=False,
on_placed=lambda leg: trailing.record_arm_leg(arm['id'], leg))
if not placed:
reason = failed[0]['reason'] if failed else 'UNKNOWN'
detail = failed[0].get('error', '') if failed else ''
_log(f'발동 전량 실패 {arm["id"]} {arm["symbol_name"]}: {reason} {detail}')
ledger.append('arm_fire_failed', {'arm_id': arm['id'], 'account': arm['account'],
'symbol': symbol, 'reason': reason,
'error': str(detail), 'stage': 'submit'})
# firing 마킹은 그대로 둔다 — 정말 한 건도 안 나갔는지 우리가 단정할 수 없다.
_notify_arm_issue(arm, f'계단 {len(plan["legs"])}단 전부 접수 실패 ({reason}) {detail}\n'
f'예약은 발동 처리 중단 상태로 남습니다 — 미체결을 확인하고 '
f'자산웹에서 취소해 주세요.')
return
registerable = [s for s in placed if s.get('ord_no')]
promoted = None
if registerable:
try:
promoted = trailing.promote_arm(arm['id'], plan['peak'], registerable, suffix)
except Exception as e:
_log(f'⚠️ 승격 실패 {arm["id"]}: {e!r}')
info = {'card_id': arm.get('card_id'), 'symbol': symbol, 'symbol_name': arm['symbol_name'],
'account': arm['account'], 'total_qty': plan['qty_used'], 'arm_qty': arm['qty'],
'qty_reduced': plan['reduced'], 'trigger_price': arm['trigger_price'],
'peak': plan['peak'], 'min_sell_price': arm.get('min_sell_price'),
'placed': placed, 'failed': failed, 'dry_run': False,
'res_id': (promoted['reservation']['id'] if promoted else None),
'register_error': None if promoted else '감시 등록 실패 — 손절선이 따라 올라가지 않습니다'}
ledger.append('arm_fired', {'arm_id': arm['id'], 'account': arm['account'], 'symbol': symbol,
'peak': plan['peak'], 'qty': plan['qty_used'],
'legs': len(placed), 'res_id': info['res_id']})
_log(f'발동 {arm["id"]} → {info["res_id"]} {arm["symbol_name"]} '
f'{len(placed)}단 · {plan["qty_used"]:,}주 · 고점 {plan["peak"]:,}')
try:
send_telegram(card.format_arm_fired(info), parse_mode=None)
except Exception as e:
_log(f'알림 실패: {e!r}')
def _expire_arms(arms: list, dry_run: bool) -> list:
"""만료된 대기 예약을 지우고 살아있는 것만 돌려준다 (2026-08-25).
스톱주문은 장 마감에 거래소가 지워주지만 대기 예약은 우리 파일에만 있으니 우리가 지운다.
감시 루프가 08:00~19:59 에만 도므로 실제 정리 시점은 **다음 거래일 첫 사이클**이다.
알림은 한 통으로 묶는다 — 여러 종목을 걸어두고 하루가 지나면 예약마다 보내면 아침에
텔레그램이 몰아친다.
"""
alive, dead = [], []
for a in arms:
(dead if trailing.is_arm_expired(a) else alive).append(a)
if not dead:
return alive
for a in dead:
_log(f'대기 예약 만료 {a["id"]} {a["symbol_name"]} '
f'(등록 {a.get("created_at", "?")[:16]} · 발동가 {a["trigger_price"]:,})')
if not dry_run:
trailing.remove_arm(a['id'], 'expired_end_of_day')
if dry_run:
return alive
lines = [f'🗓 예약 트레일링 만료 취소 — {len(dead)}건',
'장 마감이 지나 자동 취소했습니다 (스톱주문과 같은 당일 유효).']
for a in dead:
lines.append(f'· {a["symbol_name"]} ({a["symbol"]}) [{a["account"]}] '
f'{a["qty"]:,}주 · 발동가 {a["trigger_price"]:,}원 — 미발동')
lines.append('계속 노리시려면 자산웹에서 다시 등록해 주세요.')
try:
send_telegram('\n'.join(lines), parse_mode=None)
except Exception as e:
_log(f'알림 실패: {e!r}')
return alive
def _check_arms(arms: list, quotes: dict, open_by_acct: dict, sess: str, dry_run: bool) -> None:
"""대기 예약 발동 판정. 평시(미도달)엔 추가 API 콜 0 — 시세는 호출측 배치를 재사용한다.
만료분은 호출측(check)이 이미 걷어낸 상태로 넘어온다 — 시세 조회가 실패해도 정리는
돼야 하므로 순서상 앞에 둔다.
"""
# 사이드카는 마킹 전에 본다. 마킹 후 전 레그가 SidecarBlocked 로 죽으면 예약이 발동 처리
# 중단 상태로 갇힌다.
if sidecar.is_disabled():
reached = [a for a in arms
if ((quotes.get(a['symbol']) or {}).get('price') or 0) >= a['trigger_price']]
for a in reached:
_log(f'사이드카 ON — 발동 보류 {a["id"]} {a["symbol_name"]}')
_notify_arm_issue(a, '사이드카가 켜져 있어 발동을 보류했습니다 (매매 차단). '
'끄면 다음 사이클에 발동합니다.')
return
for arm in arms:
if arm.get('firing_at'):
_log(f'발동 처리 중단 상태 {arm["id"]} {arm["symbol_name"]} '
f'(firing_at={arm["firing_at"]}) — 재발동 안 함')
legs = arm.get('fired_legs') or []
nos = ', '.join(s.get('ord_no', '') for s in legs if s.get('ord_no')) or '없음'
_notify_arm_issue(arm, f'발동 처리가 중단된 상태입니다 (시작 {arm["firing_at"]}).\n'
f'접수된 주문: {nos}\n'
f'키움 미체결을 확인하고 자산웹에서 예약을 취소해 주세요. '
f'중복 매도를 막기 위해 자동 재시도하지 않습니다.')
continue
cur = (quotes.get(arm['symbol']) or {}).get('price') or 0
if cur < arm['trigger_price']:
continue # 평시 경로 — 여기서 끝나면 API 콜 0
# 미체결 조회가 실패한 계좌는 판단을 보류한다 — 예약 경로와 같은 규칙이다.
# 미체결 매도 수량을 모르는 채로 발주하면 과다 매도가 될 수 있다(0 으로 단정 금지).
open_rows = open_by_acct.get(arm['account'])
if open_rows is None:
_log(f'미체결 조회 실패로 발동 보류 {arm["id"]} {arm["symbol_name"]}')
continue
sellable, ok = _sellable_qty(arm['account'], arm['symbol'], open_rows)
if not ok:
_log(f'보유 조회 실패로 발동 보류 {arm["id"]} {arm["symbol_name"]}')
continue # 눈감고 발주하지 않는다. 다음 사이클에 다시 본다
plan = trailing.plan_arm_fire(arm, cur, sellable)
if plan['action'] == 'drop':
why = {'no_sellable': '보유 수량이 없어', 'qty_too_small': '수량이 부족해 계단을 나눌 수 없어'}
_log(f'발동 불가 {arm["id"]} {arm["symbol_name"]} — {plan["reason"]}')
if not dry_run:
trailing.remove_arm(arm['id'], plan['reason'])
send_telegram(
f'🗑 예약 트레일링 삭제 — {arm["symbol_name"]} ({arm["symbol"]})\n'
f'계좌: {arm["account"]} · 발동가 {arm["trigger_price"]:,}원 도달했지만 '
f'{why.get(plan["reason"], plan["reason"])} 발동하지 못했습니다.\n'
f'예약 {arm["qty"]:,}주 · 매도가능 {sellable:,}주',
parse_mode=None)
continue
if plan['action'] != 'fire':
_log(f'발동 보류 {arm["id"]} {arm["symbol_name"]} — {plan["reason"]}')
continue
_fire_arm(arm, plan, sess, dry_run)
def _worth_running(force: bool) -> bool:
"""예약이 있고 거래 세션인가. 루프 제어와 단발 실행이 같은 기준을 쓰게 분리."""
if not trailing.list_active():
"""볼 게 있고 거래 세션인가. 루프 제어와 단발 실행이 같은 기준을 쓰게 분리."""
if not trailing.list_active() and not trailing.list_arms():
return False
return force or session_now()[0]
def check(force: bool = False, dry_run: bool = False) -> int:
reservations = trailing.list_active()
if not reservations:
arms = trailing.list_arms()
if not reservations and not arms:
return 0
ok_sess, sess = session_now()
if not force and not ok_sess:
_log(f'{sess} — skip ({len(reservations)}건 예약 대기)')
_log(f'{sess} — skip (예약 {len(reservations)}건 · 대기 예약 {len(arms)}건)')
return 0
# NXT 단독 시간대엔 KRX 호가가 15:30 종가로 고정돼 현재가와 갭이 생긴다 → NXT 시세로 조회.
quote_exchange = 'NX' if sess in ('NXT_PRE', 'NXT_AFTER') else 'AL'
# 만료된 대기 예약은 시세보다 먼저 걷어낸다 — 시세 조회가 실패해도 정리는 돼야 하고,
# 어제 예약이 오늘 시세로 발동하는 일은 절대 없어야 한다.
if arms:
arms = _expire_arms(arms, dry_run)
if not reservations and not arms:
return 0
# 계좌별 미체결 스냅샷 (계좌당 1콜). 스톱주문도 조건 도달 전까지 미체결로 잡힌다.
open_by_acct: dict[str, list] = {}
for acct in sorted({r['account'] for r in reservations}):
for acct in sorted({r['account'] for r in reservations} | {a['account'] for a in arms}):
try:
open_by_acct[acct] = kc.get_open_orders(acct, side='sell')
except Exception as e:
@@ -246,11 +472,14 @@ def check(force: bool = False, dry_run: bool = False) -> int:
except Exception as e:
_log(f'알림 실패: {e!r}')
if not alive:
# 시세는 예약·대기 예약 심볼을 합쳐 한 번에 받는다 — ka10095 배치 1콜이라 대기 예약이
# 늘어도 API 콜이 추가되지 않는다. 실패하면 두 단계 모두 이번 사이클을 건너뛴다.
quote_codes = sorted({r['symbol'] for r, _ in alive} | {a['symbol'] for a in arms})
if not quote_codes:
return 0
try:
quotes = kc.get_watchlist_quotes([r['symbol'] for r, _ in alive], exchange=quote_exchange)
quotes = kc.get_watchlist_quotes(quote_codes, exchange=quote_exchange)
except Exception as e:
_log(f'시세 조회 실패 ({quote_exchange}): {e!r}')
return 1
@@ -330,6 +559,15 @@ def check(force: bool = False, dry_run: bool = False) -> int:
else:
_log(f' 알림 생략 (쿨다운 {FAIL_NOTICE_COOLDOWN_SEC}초 내 이미 발송)')
# 대기 예약 발동은 손절선 상향이 **끝난 뒤에** 한다. 순서가 설계다 — 상향은 이미 나간
# 주문을 지키는 동작이라 arm 쪽 어떤 예외도 그걸 선점하면 안 된다. 그래서 통째로 감싼다.
# arm 이 0건이면 이 아래는 실행되지 않아 기존 동작과 동일하다.
if arms:
try:
_check_arms(arms, quotes, open_by_acct, sess, dry_run)
except Exception as e:
_log(f'대기 예약 단계 예외 (트레일링 감시는 정상 완료): {e!r}')
if raised:
_log(f'완료 — {raised}개 계단 상향 / 예약 {len(alive)}건')
return 0
@@ -337,9 +575,29 @@ def check(force: bool = False, dry_run: bool = False) -> int:
def cmd_list() -> int:
reservations = trailing.list_active()
if not reservations:
arms = trailing.list_arms()
if not reservations and not arms:
print('트레일링 예약 없음')
return 0
if arms:
print(f'── 대기 예약 (발동가 도달 시 시작) {len(arms)}건 ──')
for a in arms:
steps = a.get('steps') or []
floor_part = f' · 최저 {a["min_sell_price"]:,}' if a.get('min_sell_price') else ''
print(f'{a["id"]} · {a["symbol_name"]}({a["symbol"]}) · {a["account"]} · '
f'{a["qty"]:,}주 · {len(steps)}단')
print(f' 발동가 {a["trigger_price"]:,}{floor_part} · 등록 {a["created_at"]}')
for s in steps:
cum = s.get('cum', 0)
cum_part = '전량' if cum >= 100 else f'누적 {_pct_s(cum)}%'
print(f' {s["n"]}단계 −{_pct_s(s["pct"])}% · {cum_part} '
f'(주수는 발동 시점 매도가능 기준으로 배분)')
if a.get('firing_at'):
legs = a.get('fired_legs') or []
nos = ', '.join(s.get('ord_no', '') for s in legs if s.get('ord_no')) or '없음'
print(f' ⚠️ 발동 처리 중단 (시작 {a["firing_at"]}) · 접수된 주문: {nos}')
if reservations and arms:
print(f'── 트레일링 예약 {len(reservations)}건 ──')
for r in reservations:
steps = r.get('steps') or []
floor_part = f' · 최저 {r["min_sell_price"]:,}' if r.get('min_sell_price') else ''
@@ -128,3 +128,5 @@
{"date": "2026-08-21", "market": "KOSPI", "market_label": "코스피", "rise": 193, "upper": 0, "fall": 683, "lower": 0, "steady": 28, "adr": 28.26, "personal": -11652, "foreign": -1760, "institutional": 2481, "captured_at": "2026-08-21T21:00:04.974633+09:00"}
{"date": "2026-08-24", "market": "KOSDAQ", "market_label": "코스닥", "rise": 921, "upper": 5, "fall": 706, "lower": 2, "steady": 96, "adr": 130.79, "personal": -2604, "foreign": 2416, "institutional": 249, "captured_at": "2026-08-24T21:00:02.141569+09:00"}
{"date": "2026-08-24", "market": "KOSPI", "market_label": "코스피", "rise": 579, "upper": 3, "fall": 286, "lower": 0, "steady": 38, "adr": 203.5, "personal": 33217, "foreign": -36691, "institutional": -12901, "captured_at": "2026-08-24T21:00:02.141569+09:00"}
{"date": "2026-08-25", "market": "KOSDAQ", "market_label": "코스닥", "rise": 1186, "upper": 4, "fall": 466, "lower": 0, "steady": 74, "adr": 255.36, "personal": -1375, "foreign": 1327, "institutional": 156, "captured_at": "2026-08-25T21:00:03.869052+09:00"}
{"date": "2026-08-25", "market": "KOSPI", "market_label": "코스피", "rise": 647, "upper": 2, "fall": 226, "lower": 1, "steady": 34, "adr": 285.9, "personal": 10509, "foreign": -38140, "institutional": 11711, "captured_at": "2026-08-25T21:00:03.869052+09:00"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
{
"cash": 11754838.588999985,
"cash": 9330274.958999984,
"initial": 100000000,
"positions": {
"010950": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 5572914.42378125,
"last_add_price": 130800
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 5640114.3750312505,
"last_add_price": 353000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 4803063.4110624995,
"last_add_price": 13420
@@ -76,12 +76,12 @@
"entry_at": "2026-08-04T09:34:08.150378+09:00",
"stop": 79324,
"target": 124784,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 4797930.468406249,
"last_add_price": 93500
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,160 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 3636137.140405993,
"last_add_price": 14290
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4804824.179093749,
"last_add_price": 198600
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 113300,
"cur_price": 111500,
"tranches": 1,
"tranche_value": 5036191.884468749,
"last_add_price": 118900
@@ -155,20 +155,20 @@
"sources": [
"interest"
],
"qty": 73,
"entry_price": 32950,
"qty": 141,
"entry_price": 34252.12765957447,
"entry_at": "2026-08-24T13:14:17.289893+09:00",
"stop": 22546,
"target": 64161,
"peak": 34700,
"stop": 23791,
"target": 65634,
"peak": 35650,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,047 도달",
"cur_price": 34500,
"tranches": 1,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 2430784.8850921993,
"last_add_price": 32950
"last_add_price": 35650
}
},
"realized_pnl": -26795815.391500026,
@@ -194,5 +194,5 @@
"138040": "2026-08-14"
},
"created_at": "2026-06-11T09:00:01.943184+09:00",
"updated_at": "2026-08-24T15:28:17.286851+09:00"
"updated_at": "2026-08-25T15:28:19.202566+09:00"
}
@@ -79,3 +79,4 @@
{"ts": "2026-08-14T13:34:15.676314+09:00", "side": "BUY", "code": "003550", "name": "LG", "price": 118900, "qty": 42, "cost": 4994549, "path": "breakout", "reason": "거래량·회전율 동반 전고점 돌파", "stop": 106512, "target": 156063, "signals": [{"label": "추세(10일선 위)", "ok": true, "detail": "118,800 vs 106,650"}, {"label": "정배열(5>10)", "ok": true, "detail": "110,480 / 106,650"}, {"label": "추세 기울기(10일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "110,480 / 107,793"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+14.2%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +36 · 기 +116 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+24%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.43% (환산) vs 평균 0.19% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": true, "detail": "고점 111,900"}, {"label": "거래량 급증", "ok": true, "detail": "662,849 (환산) vs 평균 280,124"}, {"label": "회전율 급증", "ok": true, "detail": "0.43% (환산) vs 평균 0.19%"}, {"label": "RSI", "ok": true, "detail": "69"}]}
{"ts": "2026-08-24T13:12:17.476505+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 1140, "proceeds": 5723018, "entry_price": 6333, "pnl": -1497685, "pnl_pct": -20.57, "hold_from": "2026-07-30T09:04:05.277095+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 10,215"}, {"label": "추세(10일선)", "ok": false, "detail": "5,148"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:14:17.289907+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 32950, "qty": 73, "cost": 2405711, "path": "pullback", "reason": "눌림목 지정가 33,047 도달", "stop": 22546, "target": 64161, "signals": [{"label": "추세(10일선 위)", "ok": true, "detail": "33,000 vs 28,622"}, {"label": "정배열(5>10)", "ok": true, "detail": "35,100 / 28,622"}, {"label": "추세 기울기(10일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "35,100 / 31,286"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+24.6%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -62 · 기 +65 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+106%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "3.32% (환산) vs 평균 3.74% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 45,300"}, {"label": "거래량 급증", "ok": false, "detail": "298,285 (환산) vs 평균 409,191"}, {"label": "회전율 급증", "ok": false, "detail": "3.32% (환산) vs 평균 3.74%"}, {"label": "RSI", "ok": true, "detail": "56"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 33,047 (현재 33,000)"}]}
{"ts": "2026-08-25T13:44:19.077912+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35650, "qty": 68, "cost": 2424564, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 22546, "target": 64161, "signals": [{"label": "손절선", "ok": true, "detail": "22,546 (현재 35,650)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 64,161"}, {"label": "추세(10일선)", "ok": true, "detail": "28,887"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -118 · 기 +90"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,565"}]}
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3949543.523644232,
"last_add_price": 136000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3504336.4241500003,
"last_add_price": 13420
@@ -53,14 +53,14 @@
"qty": 77,
"entry_price": 91929.87012987013,
"entry_at": "2026-08-04T09:50:08.969520+09:00",
"stop": 109994,
"stop": 111762,
"target": 108977,
"peak": 121000,
"peak": 123500,
"trailing_on": true,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3560762.7001641323,
"last_add_price": 93500
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,170 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2094069.1444097043,
"last_add_price": 13830
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 5025448.547031702,
"last_add_price": 196400
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3615738.9231707016,
"last_add_price": 415500
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1442538.400216884,
"last_add_price": 35800
@@ -160,12 +160,12 @@
"entry_at": "2026-08-24T13:14:20.866190+09:00",
"stop": 17760,
"target": 20860,
"peak": 19110,
"peak": 19250,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 19,655 도달",
"cur_price": 19100,
"cur_price": 19110,
"tranches": 1,
"tranche_value": 5442628.740343751,
"last_add_price": 19000
@@ -193,5 +193,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-11T09:00:01.944232+09:00",
"updated_at": "2026-08-24T15:28:21.274422+09:00"
"updated_at": "2026-08-25T15:28:22.679441+09:00"
}
@@ -1,5 +1,5 @@
{
"cash": 23605266.561000064,
"cash": 18298970.736000065,
"initial": 100000000,
"positions": {
"138040": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 118,115 도달",
"cur_price": 115000,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 5549046.703468754,
"last_add_price": 120500
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 122,665 도달",
"cur_price": 128800,
"cur_price": 130800,
"tranches": 2,
"tranche_value": 5150762.675171776,
"last_add_price": 125200
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3940896.531574532,
"last_add_price": 136000
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3497500.5799993444,
"last_add_price": 13420
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,947 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2091765.9324469643,
"last_add_price": 13830
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 5012093.7710951045,
"last_add_price": 196400
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3599778.576622015,
"last_add_price": 415500
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 167,
"entry_price": 31900,
"qty": 329,
"entry_price": 32318.54103343465,
"entry_at": "2026-08-24T13:12:20.462989+09:00",
"stop": 29900,
"target": 34900,
"peak": 32050,
"target": 35946,
"peak": 32800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 31,939 도달",
"cur_price": 31950,
"tranches": 1,
"cur_price": 32350,
"tranches": 2,
"tranche_value": 5336831.603500004,
"last_add_price": 31900
"last_add_price": 32750
}
},
"realized_pnl": -14520223.654500023,
@@ -194,5 +194,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-11T09:00:01.944251+09:00",
"updated_at": "2026-08-24T15:28:21.277074+09:00"
"updated_at": "2026-08-25T15:28:22.681858+09:00"
}
@@ -98,3 +98,4 @@
{"ts": "2026-08-13T14:02:16.527631+09:00", "side": "BUY", "code": "214450", "name": "파마리서치", "price": 415500, "qty": 8, "cost": 3324499, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 352720, "target": 477170, "signals": [{"label": "손절선", "ok": true, "detail": "352,720 (현재 415,500)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 477,170"}, {"label": "추세(10일선)", "ok": true, "detail": "312,900"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +43 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 415,052"}]}
{"ts": "2026-08-24T13:12:20.462527+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 631, "proceeds": 3167741, "entry_price": 6333, "pnl": -828989, "pnl_pct": -20.57, "hold_from": "2026-07-30T09:04:06.863063+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 8,274"}, {"label": "추세(10일선)", "ok": false, "detail": "5,148"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:20.462991+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 31900, "qty": 167, "cost": 5328099, "path": "pullback", "reason": "눌림목 지정가 31,939 도달", "stop": 29900, "target": 34900, "signals": [{"label": "추세(10일선 위)", "ok": true, "detail": "31,850 vs 31,795"}, {"label": "정배열(5>10)", "ok": true, "detail": "32,390 / 31,795"}, {"label": "추세 기울기(10일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "32,390 / 30,876"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+3.6%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -892 · 기 +171 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+37%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.24% (환산) vs 평균 0.34% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 33,850"}, {"label": "거래량 급증", "ok": false, "detail": "1,768,231 (환산) vs 평균 2,489,243"}, {"label": "회전율 급증", "ok": false, "detail": "0.24% (환산) vs 평균 0.34%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 31,939 (현재 31,850)"}]}
{"ts": "2026-08-25T11:36:17.992170+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 32750, "qty": 162, "cost": 5306296, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 29900, "target": 34900, "signals": [{"label": "손절선", "ok": true, "detail": "29,900 (현재 32,750)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 34,900"}, {"label": "추세(10일선)", "ok": true, "detail": "31,885"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -1,322 · 기 +436"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 32,722"}]}
@@ -1,28 +1,7 @@
{
"cash": 38865967.97700004,
"cash": 28442772.680500045,
"initial": 100000000,
"positions": {
"252990": {
"code": "252990",
"name": "샘씨엔에스",
"sources": [
"auto"
],
"qty": 333,
"entry_price": 13905.405405405405,
"entry_at": "2026-08-05T09:18:09.783202+09:00",
"stop": 11576,
"target": 18564,
"peak": 16270,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,947 도달",
"cur_price": 13310,
"tranches": 2,
"tranche_value": 2325831.4188620616,
"last_add_price": 14270
},
"068270": {
"code": "068270",
"name": "셀트리온",
@@ -39,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4973576.381781252,
"last_add_price": 196400
@@ -60,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 4403970.424534555,
"last_add_price": 416500
@@ -81,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 35,709 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1699505.011186723,
"last_add_price": 35800
@@ -102,7 +81,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 157,486 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1942551.0603990993,
"last_add_price": 144900
@@ -123,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 7,771 도달",
"cur_price": 5060,
"cur_price": 5040,
"tranches": 1,
"tranche_value": 2088749.9404521286,
"last_add_price": 5500
@@ -134,20 +113,20 @@
"sources": [
"auto"
],
"qty": 42,
"entry_price": 115000,
"qty": 83,
"entry_price": 116580.72289156627,
"entry_at": "2026-08-20T09:02:17.452076+09:00",
"stop": 111200,
"target": 122600,
"peak": 117500,
"target": 127342,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 117,922 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4911248.4428750025,
"last_add_price": 115000
"last_add_price": 118200
},
"316140": {
"code": "316140",
@@ -155,24 +134,45 @@
"sources": [
"auto"
],
"qty": 153,
"entry_price": 31550,
"qty": 302,
"entry_price": 31969.370860927153,
"entry_at": "2026-08-24T09:02:22.276742+09:00",
"stop": 29900,
"target": 34850,
"peak": 32050,
"target": 36108,
"peak": 32800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 31,946 도달",
"cur_price": 31950,
"tranches": 1,
"cur_price": 32350,
"tranches": 2,
"tranche_value": 4850367.628093753,
"last_add_price": 31550
"last_add_price": 32400
},
"030000": {
"code": "030000",
"name": "제일기획",
"sources": [
"auto"
],
"qty": 253,
"entry_price": 19190,
"entry_at": "2026-08-25T12:16:22.494706+09:00",
"stop": 18112,
"target": 21346,
"peak": 19250,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 20,031 도달",
"cur_price": 19110,
"tranches": 1,
"tranche_value": 4858670.683812503,
"last_add_price": 19190
}
},
"realized_pnl": -21614655.005500037,
"closed_trades": 50,
"realized_pnl": -22137995.54650004,
"closed_trades": 51,
"wins": 4,
"peak_equity": 101695667.93550003,
"max_drawdown": 0.24548980547857824,
@@ -191,7 +191,7 @@
"105560": "2026-07-20",
"086790": "2026-07-21",
"003680": "2026-08-19",
"252990": "2026-07-28",
"252990": "2026-08-25",
"010950": "2026-08-03",
"090850": "2026-08-05",
"161890": "2026-07-30",
@@ -201,5 +201,5 @@
"194370": "2026-08-24"
},
"created_at": "2026-06-11T09:00:01.944270+09:00",
"updated_at": "2026-08-24T15:28:21.279713+09:00"
"updated_at": "2026-08-25T15:28:22.684262+09:00"
}
@@ -140,3 +140,7 @@
{"ts": "2026-08-20T09:16:21.642669+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 47, "cost": 1682852, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 25688, "target": 48823, "signals": [{"label": "손절선", "ok": true, "detail": "25,688 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 48,823"}, {"label": "추세(20일선)", "ok": true, "detail": "26,208"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T09:00:21.444249+09:00", "side": "SELL", "code": "194370", "name": "제이에스코퍼레이션", "price": 12790, "qty": 653, "proceeds": 8335584, "entry_price": 13160, "pnl": -259135, "pnl_pct": -2.81, "hold_from": "2026-08-03T12:00:11.776417+09:00", "reason": "시간손절 — 20일 보유·진전 없음(자본 회전)", "partial": false, "signals": [{"label": "손절선", "ok": true, "detail": "11,874 (현재 12,750)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 15,732"}, {"label": "추세(20일선)", "ok": true, "detail": "11,913"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -59 · 기 -20"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T09:02:22.276759+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 31550, "qty": 153, "cost": 4827874, "path": "pullback", "reason": "눌림목 지정가 31,946 도달", "stop": 29900, "target": 34850, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "31,500 vs 31,388"}, {"label": "정배열(5>20)", "ok": true, "detail": "32,320 / 31,388"}, {"label": "추세 기울기(20일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "32,320 / 30,870"}, {"label": "상대강도(시장대비)", "ok": false, "detail": "-0.2%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -988 · 기 +144 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+39%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.08% (환산) vs 평균 0.34% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 33,850"}, {"label": "거래량 급증", "ok": false, "detail": "551,940 (환산) vs 평균 2,489,243"}, {"label": "회전율 급증", "ok": false, "detail": "0.08% (환산) vs 평균 0.34%"}, {"label": "RSI", "ok": true, "detail": "51"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 31,946 (현재 31,500)"}]}
{"ts": "2026-08-25T09:20:20.388186+09:00", "side": "SELL", "code": "252990", "name": "샘씨엔에스", "price": 12360, "qty": 333, "proceeds": 4107854, "entry_price": 13905, "pnl": -523341, "pnl_pct": -11.11, "hold_from": "2026-08-05T09:18:09.783202+09:00", "reason": "시간손절 — 20일 보유·진전 없음(자본 회전)", "partial": false, "signals": [{"label": "손절선", "ok": true, "detail": "11,576 (현재 12,370)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 18,564"}, {"label": "추세(20일선)", "ok": false, "detail": "12,694"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +34 · 기 -30"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-25T09:34:17.879110+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 41, "cost": 4846927, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 122600, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 122,600"}, {"label": "추세(20일선)", "ok": true, "detail": "113,465"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 117,689"}]}
{"ts": "2026-08-25T09:46:20.069247+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 32400, "qty": 149, "cost": 4828324, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 29900, "target": 34850, "signals": [{"label": "손절선", "ok": true, "detail": "29,900 (현재 32,400)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 34,850"}, {"label": "추세(20일선)", "ok": true, "detail": "31,432"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -1,322 · 기 +436"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 32,372"}]}
{"ts": "2026-08-25T12:16:22.494708+09:00", "side": "BUY", "code": "030000", "name": "제일기획", "price": 19190, "qty": 253, "cost": 4855798, "path": "pullback", "reason": "눌림목 지정가 20,031 도달", "stop": 18112, "target": 21346, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "19,180 vs 18,864"}, {"label": "정배열(5>20)", "ok": true, "detail": "19,622 / 18,864"}, {"label": "추세 기울기(20일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "19,622 / 19,362"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+0.8%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +274 · 기 -97 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+31%"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.21% (환산) vs 평균 0.62% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 21,150"}, {"label": "거래량 급증", "ok": false, "detail": "241,100 (환산) vs 평균 711,709"}, {"label": "회전율 급증", "ok": false, "detail": "0.21% (환산) vs 평균 0.62%"}, {"label": "RSI", "ok": true, "detail": "51"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 20,031 (현재 19,180)"}]}
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 118,115 도달",
"cur_price": 115000,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 5368293.320125001,
"last_add_price": 120500
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 122,665 도달",
"cur_price": 128800,
"cur_price": 130800,
"tranches": 2,
"tranche_value": 5180051.180047499,
"last_add_price": 125200
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 4464385.527789056,
"last_add_price": 13420
@@ -74,14 +74,14 @@
"qty": 98,
"entry_price": 91918.36734693877,
"entry_at": "2026-08-04T09:50:09.564914+09:00",
"stop": 109994,
"stop": 111762,
"target": 108966,
"peak": 121000,
"peak": 123500,
"trailing_on": true,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 4532723.825333739,
"last_add_price": 93500
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,975 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2418915.1277636583,
"last_add_price": 14290
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 5253404.499,
"last_add_price": 196400
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 4617746.513836046,
"last_add_price": 415500
@@ -160,12 +160,12 @@
"entry_at": "2026-08-20T10:34:23.489978+09:00",
"stop": 27388,
"target": 50523,
"peak": 35600,
"peak": 35650,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 35,709 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 1,
"tranche_value": 1917039.5768596022,
"last_add_price": 35100
@@ -194,5 +194,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-11T09:00:01.944290+09:00",
"updated_at": "2026-08-24T15:28:21.282132+09:00"
"updated_at": "2026-08-25T15:28:22.686564+09:00"
}
@@ -1,5 +1,5 @@
{
"cash": 7247132.073499956,
"cash": 2890728.7109999554,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 5184250.1290937485,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 118,115 도달",
"cur_price": 115000,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4675255.455593748,
"last_add_price": 120500
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 122,665 도달",
"cur_price": 128800,
"cur_price": 130800,
"tranches": 2,
"tranche_value": 4550435.925312498,
"last_add_price": 125200
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 4604300.898906248,
"last_add_price": 136000
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 4446388.7381874975,
"last_add_price": 13420
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,947 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 3449195.5386017757,
"last_add_price": 13830
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 35,709 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 2315542.8746463708,
"last_add_price": 35800
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 136,
"entry_price": 31900,
"qty": 269,
"entry_price": 32320.260223048328,
"entry_at": "2026-08-24T13:12:20.472222+09:00",
"stop": 29900,
"target": 37900,
"peak": 32050,
"target": 39581,
"peak": 32800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 31,939 도달",
"cur_price": 31950,
"tranches": 1,
"cur_price": 32350,
"tranches": 2,
"tranche_value": 4361206.427093747,
"last_add_price": 31900
"last_add_price": 32750
}
},
"realized_pnl": -31731326.068000022,
@@ -199,5 +199,5 @@
"161890": "2026-07-30"
},
"created_at": "2026-06-15T14:55:00.582771+09:00",
"updated_at": "2026-08-24T15:28:21.284596+09:00"
"updated_at": "2026-08-25T15:28:22.688853+09:00"
}
@@ -109,3 +109,4 @@
{"ts": "2026-08-20T09:16:21.647472+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 64, "cost": 2291544, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 23118, "target": 64246, "signals": [{"label": "손절선", "ok": true, "detail": "23,118 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 64,246"}, {"label": "추세(20일선)", "ok": true, "detail": "26,208"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T13:12:20.471895+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 1055, "proceeds": 5296302, "entry_price": 6333, "pnl": -1386050, "pnl_pct": -20.58, "hold_from": "2026-07-30T09:04:07.466771+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 10,215"}, {"label": "추세(20일선)", "ok": true, "detail": "4,754"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:20.472224+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 31900, "qty": 136, "cost": 4339051, "path": "pullback", "reason": "눌림목 지정가 31,939 도달", "stop": 29900, "target": 37900, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "31,850 vs 31,405"}, {"label": "정배열(5>20)", "ok": true, "detail": "32,390 / 31,405"}, {"label": "추세 기울기(20일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "32,390 / 30,876"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+3.6%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -892 · 기 +171 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+37%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.24% (환산) vs 평균 0.34% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 33,850"}, {"label": "거래량 급증", "ok": false, "detail": "1,768,231 (환산) vs 평균 2,489,243"}, {"label": "회전율 급증", "ok": false, "detail": "0.24% (환산) vs 평균 0.34%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 31,939 (현재 31,850)"}]}
{"ts": "2026-08-25T11:36:18.000274+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 32750, "qty": 133, "cost": 4356403, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 29900, "target": 37900, "signals": [{"label": "손절선", "ok": true, "detail": "29,900 (현재 32,750)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 37,900"}, {"label": "추세(20일선)", "ok": true, "detail": "31,450"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -1,322 · 기 +436"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 32,722"}]}
@@ -1,5 +1,5 @@
{
"cash": 9899146.234499946,
"cash": 5116929.009499946,
"initial": 100000000,
"positions": {
"138040": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 118,115 도달",
"cur_price": 115000,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 5057738.410843747,
"last_add_price": 120500
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 122,665 도달",
"cur_price": 128800,
"cur_price": 130800,
"tranches": 2,
"tranche_value": 5030428.7588749975,
"last_add_price": 125200
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 5098537.405531247,
"last_add_price": 136000
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 4920739.488124997,
"last_add_price": 13420
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,947 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 3814821.8249811833,
"last_add_price": 13830
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 5119873.139312496,
"last_add_price": 417500
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 35,709 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 2545799.7712668725,
"last_add_price": 35800
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 149,
"entry_price": 31900,
"qty": 295,
"entry_price": 32320.677966101695,
"entry_at": "2026-08-24T13:12:20.475258+09:00",
"stop": 29900,
"target": 34900,
"peak": 32050,
"target": 35952,
"peak": 32800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 31,939 도달",
"cur_price": 31950,
"tranches": 1,
"cur_price": 32350,
"tranches": 2,
"tranche_value": 4784721.199968747,
"last_add_price": 31900
"last_add_price": 32750
}
},
"realized_pnl": -23072340.99650003,
@@ -199,5 +199,5 @@
"161890": "2026-07-30"
},
"created_at": "2026-06-15T14:55:00.582787+09:00",
"updated_at": "2026-08-24T15:28:21.286982+09:00"
"updated_at": "2026-08-25T15:28:22.691063+09:00"
}
@@ -121,3 +121,4 @@
{"ts": "2026-08-20T09:16:21.649864+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 71, "cost": 2542181, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 23118, "target": 48823, "signals": [{"label": "손절선", "ok": true, "detail": "23,118 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 48,823"}, {"label": "추세(20일선)", "ok": true, "detail": "26,208"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T13:12:20.475051+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 1167, "proceeds": 5858563, "entry_price": 6333, "pnl": -1533135, "pnl_pct": -20.57, "hold_from": "2026-07-30T09:04:07.473311+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 8,274"}, {"label": "추세(20일선)", "ok": true, "detail": "4,754"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:20.475260+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 31900, "qty": 149, "cost": 4753813, "path": "pullback", "reason": "눌림목 지정가 31,939 도달", "stop": 29900, "target": 34900, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "31,850 vs 31,405"}, {"label": "정배열(5>20)", "ok": true, "detail": "32,390 / 31,405"}, {"label": "추세 기울기(20일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "32,390 / 30,876"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+3.6%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -892 · 기 +171 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+37%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.24% (환산) vs 평균 0.34% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 33,850"}, {"label": "거래량 급증", "ok": false, "detail": "1,768,231 (환산) vs 평균 2,489,243"}, {"label": "회전율 급증", "ok": false, "detail": "0.24% (환산) vs 평균 0.34%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 31,939 (현재 31,850)"}]}
{"ts": "2026-08-25T11:36:18.002977+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 32750, "qty": 146, "cost": 4782217, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 29900, "target": 34900, "signals": [{"label": "손절선", "ok": true, "detail": "29,900 (현재 32,750)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 34,900"}, {"label": "추세(20일선)", "ok": true, "detail": "31,450"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -1,322 · 기 +436"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 32,722"}]}
@@ -1,5 +1,5 @@
{
"cash": 9167441.589999957,
"cash": 4712773.489999956,
"initial": 100000000,
"positions": {
"138040": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 118,115 도달",
"cur_price": 115000,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4798325.220406247,
"last_add_price": 120500
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 122,665 도달",
"cur_price": 128800,
"cur_price": 130800,
"tranches": 2,
"tranche_value": 4697478.874937497,
"last_add_price": 125200
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 4762406.753156248,
"last_add_price": 136000
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 4594021.1274374975,
"last_add_price": 13420
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,947 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 3561242.7967011826,
"last_add_price": 13830
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 4781236.946562497,
"last_add_price": 417500
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 35,709 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 2377721.795719703,
"last_add_price": 35800
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 140,
"entry_price": 31900,
"qty": 276,
"entry_price": 32318.840579710144,
"entry_at": "2026-08-24T13:12:20.478387+09:00",
"stop": 29900,
"target": 35900,
"peak": 32050,
"target": 37157,
"peak": 32800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 31,939 도달",
"cur_price": 31950,
"tranches": 1,
"cur_price": 32350,
"tranches": 2,
"tranche_value": 4467750.718124997,
"last_add_price": 31900
"last_add_price": 32750
}
},
"realized_pnl": -28164339.587000035,
@@ -199,5 +199,5 @@
"161890": "2026-07-30"
},
"created_at": "2026-06-15T14:55:00.582803+09:00",
"updated_at": "2026-08-24T15:28:21.289314+09:00"
"updated_at": "2026-08-25T15:28:22.693234+09:00"
}
@@ -115,3 +115,4 @@
{"ts": "2026-08-20T09:16:21.652201+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 66, "cost": 2363154, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 23118, "target": 53964, "signals": [{"label": "손절선", "ok": true, "detail": "23,118 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 53,964"}, {"label": "추세(20일선)", "ok": true, "detail": "26,208"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T13:12:20.478005+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 1090, "proceeds": 5472009, "entry_price": 6333, "pnl": -1432067, "pnl_pct": -20.58, "hold_from": "2026-07-30T09:04:07.479053+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 8,921"}, {"label": "추세(20일선)", "ok": true, "detail": "4,754"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:20.478389+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 31900, "qty": 140, "cost": 4466670, "path": "pullback", "reason": "눌림목 지정가 31,939 도달", "stop": 29900, "target": 35900, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "31,850 vs 31,405"}, {"label": "정배열(5>20)", "ok": true, "detail": "32,390 / 31,405"}, {"label": "추세 기울기(20일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "32,390 / 30,876"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+3.6%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -892 · 기 +171 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+37%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.24% (환산) vs 평균 0.34% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 33,850"}, {"label": "거래량 급증", "ok": false, "detail": "1,768,231 (환산) vs 평균 2,489,243"}, {"label": "회전율 급증", "ok": false, "detail": "0.24% (환산) vs 평균 0.34%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 31,939 (현재 31,850)"}]}
{"ts": "2026-08-25T11:36:18.005533+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 32750, "qty": 136, "cost": 4454668, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 29900, "target": 35900, "signals": [{"label": "손절선", "ok": true, "detail": "29,900 (현재 32,750)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 35,900"}, {"label": "추세(20일선)", "ok": true, "detail": "31,450"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -1,322 · 기 +436"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 32,722"}]}
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 118,115 도달",
"cur_price": 115000,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 5120146.001656252,
"last_add_price": 120500
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 122,665 도달",
"cur_price": 128800,
"cur_price": 130800,
"tranches": 2,
"tranche_value": 4929642.288776176,
"last_add_price": 125200
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 4296364.655919675,
"last_add_price": 13420
@@ -74,14 +74,14 @@
"qty": 94,
"entry_price": 91917.02127659574,
"entry_at": "2026-08-04T09:48:09.782722+09:00",
"stop": 109994,
"stop": 111762,
"target": 108965,
"peak": 121000,
"peak": 123500,
"trailing_on": true,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 4361392.052247569,
"last_add_price": 93500
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,975 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2326313.326987872,
"last_add_price": 14290
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 4443615.9921114575,
"last_add_price": 415500
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 35,709 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1744609.622982949,
"last_add_price": 35800
@@ -165,7 +165,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 157,407 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1989854.351411668,
"last_add_price": 145000
@@ -200,5 +200,5 @@
"161890": "2026-07-30"
},
"created_at": "2026-06-15T14:55:00.582834+09:00",
"updated_at": "2026-08-24T15:28:21.291595+09:00"
"updated_at": "2026-08-25T15:28:22.695362+09:00"
}
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 4489026.732352336,
"last_add_price": 13420
@@ -32,14 +32,14 @@
"qty": 98,
"entry_price": 91714.28571428571,
"entry_at": "2026-08-04T09:34:09.331555+09:00",
"stop": 109994,
"stop": 111762,
"target": 104500,
"peak": 121000,
"peak": 123500,
"trailing_on": true,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 4576294.319379674,
"last_add_price": 93500
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,033 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2154436.3245398356,
"last_add_price": 13640
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 5207451.256718745,
"last_add_price": 198600
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 4536055.463688828,
"last_add_price": 417500
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1773404.3510667775,
"last_add_price": 35800
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 147,729 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 2027018.6354019148,
"last_add_price": 144900
@@ -160,12 +160,12 @@
"entry_at": "2026-08-20T10:00:18.528052+09:00",
"stop": 45302,
"target": 55335,
"peak": 50700,
"peak": 50900,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 50600,
"cur_price": 50400,
"tranches": 2,
"tranche_value": 4928101.458627134,
"last_add_price": 50000
@@ -200,5 +200,5 @@
"003550": "2026-08-19"
},
"created_at": "2026-06-15T14:55:00.582849+09:00",
"updated_at": "2026-08-24T15:28:21.293850+09:00"
"updated_at": "2026-08-25T15:28:22.697446+09:00"
}
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 4992940.599795671,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 4188989.7646745183,
"last_add_price": 13420
@@ -53,14 +53,14 @@
"qty": 92,
"entry_price": 91711.95652173914,
"entry_at": "2026-08-04T09:34:09.335258+09:00",
"stop": 109994,
"stop": 111762,
"target": 117283,
"peak": 121000,
"peak": 123500,
"trailing_on": true,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 4268719.0892993,
"last_add_price": 93500
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,033 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2008681.3553691048,
"last_add_price": 13640
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4855136.307499998,
"last_add_price": 198600
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1684859.3510976394,
"last_add_price": 35800
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 147,729 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1925810.8724292808,
"last_add_price": 144900
@@ -160,12 +160,12 @@
"entry_at": "2026-08-20T10:00:18.530498+09:00",
"stop": 45301,
"target": 61354,
"peak": 50700,
"peak": 50900,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 50600,
"cur_price": 50400,
"tranches": 2,
"tranche_value": 4686399.784966664,
"last_add_price": 50000
@@ -200,5 +200,5 @@
"003550": "2026-08-19"
},
"created_at": "2026-06-15T14:55:00.582864+09:00",
"updated_at": "2026-08-24T15:28:21.296058+09:00"
"updated_at": "2026-08-25T15:28:22.699497+09:00"
}
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3813943.0504600187,
"last_add_price": 350000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3759463.8630866953,
"last_add_price": 136000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3342042.065213833,
"last_add_price": 13420
@@ -76,12 +76,12 @@
"entry_at": "2026-08-04T09:34:09.921371+09:00",
"stop": 80361,
"target": 125821,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3414906.320549933,
"last_add_price": 93500
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,170 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 1996058.5440871862,
"last_add_price": 13830
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4827704.554226948,
"last_add_price": 198600
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1375604.0354715528,
"last_add_price": 35800
@@ -160,12 +160,12 @@
"entry_at": "2026-08-24T13:14:21.476706+09:00",
"stop": 17760,
"target": 22720,
"peak": 19110,
"peak": 19250,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 19,655 도달",
"cur_price": 19100,
"cur_price": 19110,
"tranches": 1,
"tranche_value": 5197409.522968752,
"last_add_price": 19000
@@ -196,5 +196,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-15T14:55:00.582879+09:00",
"updated_at": "2026-08-24T15:28:23.012141+09:00"
"updated_at": "2026-08-25T15:28:24.455035+09:00"
}
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3759463.8630866953,
"last_add_price": 136000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3342042.065213833,
"last_add_price": 13420
@@ -53,14 +53,14 @@
"qty": 73,
"entry_price": 91726.02739726027,
"entry_at": "2026-08-04T09:34:09.933331+09:00",
"stop": 109994,
"stop": 111762,
"target": 114456,
"peak": 121000,
"peak": 123500,
"trailing_on": true,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3414906.320549933,
"last_add_price": 93500
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,170 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 1996058.5440871862,
"last_add_price": 13830
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4827704.554226948,
"last_add_price": 198600
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3439103.463858052,
"last_add_price": 417500
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1371827.8681296934,
"last_add_price": 35800
@@ -160,12 +160,12 @@
"entry_at": "2026-08-24T13:14:22.528454+09:00",
"stop": 17760,
"target": 21480,
"peak": 19110,
"peak": 19250,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 19,655 도달",
"cur_price": 19100,
"cur_price": 19110,
"tranches": 1,
"tranche_value": 5176941.127531253,
"last_add_price": 19000
@@ -196,5 +196,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-15T14:55:00.582910+09:00",
"updated_at": "2026-08-24T15:28:23.024671+09:00"
"updated_at": "2026-08-25T15:28:24.465147+09:00"
}
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3812991.5556229833,
"last_add_price": 136000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3393650.506937234,
"last_add_price": 13420
@@ -53,14 +53,14 @@
"qty": 75,
"entry_price": 91726.66666666667,
"entry_at": "2026-08-04T09:34:09.942366+09:00",
"stop": 109994,
"stop": 111762,
"target": 108774,
"peak": 121000,
"peak": 123500,
"trailing_on": true,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3469610.992924529,
"last_add_price": 93500
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,170 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2028402.8880774078,
"last_add_price": 13830
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4907695.663916428,
"last_add_price": 198600
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3458120.1956040077,
"last_add_price": 417500
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1380001.3303458472,
"last_add_price": 35800
@@ -160,12 +160,12 @@
"entry_at": "2026-08-24T13:14:22.536474+09:00",
"stop": 17760,
"target": 20860,
"peak": 19110,
"peak": 19250,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 19,655 도달",
"cur_price": 19100,
"cur_price": 19110,
"tranches": 1,
"tranche_value": 5205751.283343751,
"last_add_price": 19000
@@ -175,7 +175,7 @@
"closed_trades": 32,
"wins": 2,
"peak_equity": 100000000,
"max_drawdown": 0.17355132621999994,
"max_drawdown": 0.17443687516499995,
"last_exit": {
"030000": "2026-08-13",
"086790": "2026-07-29",
@@ -196,5 +196,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-15T14:55:00.582926+09:00",
"updated_at": "2026-08-24T15:28:23.032139+09:00"
"updated_at": "2026-08-25T15:28:24.474135+09:00"
}
@@ -1,5 +1,5 @@
{
"cash": 24200387.246999994,
"cash": 19226841.326999992,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 5085796.90859035,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 4271897.688361821,
"last_add_price": 13420
@@ -53,14 +53,14 @@
"qty": 93,
"entry_price": 91933.33333333333,
"entry_at": "2026-08-04T09:50:10.213035+09:00",
"stop": 109994,
"stop": 111762,
"target": 117505,
"peak": 121000,
"peak": 123500,
"trailing_on": true,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 4327959.889890211,
"last_add_price": 93500
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,170 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2219137.008862232,
"last_add_price": 13830
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4934881.2159687495,
"last_add_price": 196400
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1725875.4362712654,
"last_add_price": 35800
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 147,729 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1972692.6896559393,
"last_add_price": 144900
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 264,
"entry_price": 18840,
"qty": 523,
"entry_price": 19018.27915869981,
"entry_at": "2026-08-20T10:06:19.265748+09:00",
"stop": 17760,
"target": 22080,
"peak": 19110,
"stop": 17940,
"target": 22252,
"peak": 19250,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 19,640 도달",
"cur_price": 19100,
"tranches": 1,
"cur_price": 19110,
"tranches": 2,
"tranche_value": 4987614.581937499,
"last_add_price": 18840
"last_add_price": 19200
}
},
"realized_pnl": -24488397.225000024,
@@ -198,5 +198,5 @@
"003550": "2026-08-19"
},
"created_at": "2026-06-15T14:55:00.583136+09:00",
"updated_at": "2026-08-24T15:28:23.039732+09:00"
"updated_at": "2026-08-25T15:28:24.480761+09:00"
}
@@ -121,3 +121,4 @@
{"ts": "2026-08-20T09:16:22.841712+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 48, "cost": 1718658, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 25688, "target": 56535, "signals": [{"label": "손절선", "ok": true, "detail": "25,688 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 56,535"}, {"label": "추세(20일선)", "ok": true, "detail": "26,208"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-20T10:00:19.684004+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5360, "qty": 793, "proceeds": 4242192, "entry_price": 6333, "pnl": -780512, "pnl_pct": -15.36, "hold_from": "2026-07-30T09:04:07.509224+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,362 (현재 5,360)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 9,244"}, {"label": "추세(20일선)", "ok": true, "detail": "4,770"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +53 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-20T10:06:19.265752+09:00", "side": "BUY", "code": "030000", "name": "제일기획", "price": 18840, "qty": 264, "cost": 4974506, "path": "pullback", "reason": "눌림목 지정가 19,640 도달", "stop": 17760, "target": 22080, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "18,860 vs 18,848"}, {"label": "정배열(5>20)", "ok": true, "detail": "19,558 / 18,848"}, {"label": "추세 기울기(20일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "19,558 / 19,356"}, {"label": "상대강도(시장대비)", "ok": false, "detail": "-3.8%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +95 · 기 -424 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+33%"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.42% (환산) vs 평균 0.62% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 21,150"}, {"label": "거래량 급증", "ok": false, "detail": "481,892 (환산) vs 평균 711,709"}, {"label": "회전율 급증", "ok": false, "detail": "0.42% (환산) vs 평균 0.62%"}, {"label": "RSI", "ok": true, "detail": "48"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 19,640 (현재 18,860)"}]}
{"ts": "2026-08-25T12:06:22.970717+09:00", "side": "BUY", "code": "030000", "name": "제일기획", "price": 19200, "qty": 259, "cost": 4973546, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 17760, "target": 22080, "signals": [{"label": "손절선", "ok": true, "detail": "17,760 (현재 19,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 22,080"}, {"label": "추세(20일선)", "ok": true, "detail": "18,864"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +274 · 기 -97"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 19,199"}]}
@@ -1,5 +1,5 @@
{
"cash": 24062611.094000004,
"cash": 18952844.744000003,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3889733.683715179,
"last_add_price": 353000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 118,115 도달",
"cur_price": 115000,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 5327150.576000001,
"last_add_price": 120500
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 122,665 도달",
"cur_price": 128800,
"cur_price": 130800,
"tranches": 2,
"tranche_value": 4940372.790080874,
"last_add_price": 125200
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3791237.2526261737,
"last_add_price": 136000
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,947 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2020520.412274148,
"last_add_price": 13830
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4842897.369596543,
"last_add_price": 196400
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 35,709 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1352658.7410761528,
"last_add_price": 35800
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 160,
"entry_price": 31900,
"qty": 316,
"entry_price": 32319.620253164558,
"entry_at": "2026-08-24T13:12:22.273409+09:00",
"stop": 29900,
"target": 37900,
"peak": 32050,
"target": 39578,
"peak": 32800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 31,939 도달",
"cur_price": 31950,
"tranches": 1,
"cur_price": 32350,
"tranches": 2,
"tranche_value": 5133126.668375,
"last_add_price": 31900
"last_add_price": 32750
}
},
"realized_pnl": -19279151.44500002,
@@ -197,5 +197,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-15T14:55:00.583184+09:00",
"updated_at": "2026-08-24T15:28:23.045678+09:00"
"updated_at": "2026-08-25T15:28:24.487187+09:00"
}
@@ -98,3 +98,4 @@
{"ts": "2026-08-20T09:16:22.848001+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 37, "cost": 1324799, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 23118, "target": 64246, "signals": [{"label": "손절선", "ok": true, "detail": "23,118 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 64,246"}, {"label": "추세(10일선)", "ok": true, "detail": "28,922"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T13:12:22.272858+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 606, "proceeds": 3042236, "entry_price": 6333, "pnl": -796180, "pnl_pct": -20.58, "hold_from": "2026-07-30T09:04:07.512664+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 10,215"}, {"label": "추세(10일선)", "ok": false, "detail": "5,148"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:22.273412+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 31900, "qty": 160, "cost": 5104766, "path": "pullback", "reason": "눌림목 지정가 31,939 도달", "stop": 29900, "target": 37900, "signals": [{"label": "추세(10일선 위)", "ok": true, "detail": "31,850 vs 31,795"}, {"label": "정배열(5>10)", "ok": true, "detail": "32,390 / 31,795"}, {"label": "추세 기울기(10일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "32,390 / 30,876"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+3.6%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -892 · 기 +171 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+37%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.24% (환산) vs 평균 0.34% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 33,850"}, {"label": "거래량 급증", "ok": false, "detail": "1,768,231 (환산) vs 평균 2,489,243"}, {"label": "회전율 급증", "ok": false, "detail": "0.24% (환산) vs 평균 0.34%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 31,939 (현재 31,850)"}]}
{"ts": "2026-08-25T11:36:19.771828+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 32750, "qty": 156, "cost": 5109766, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 29900, "target": 37900, "signals": [{"label": "손절선", "ok": true, "detail": "29,900 (현재 32,750)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 37,900"}, {"label": "추세(10일선)", "ok": true, "detail": "31,885"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -1,322 · 기 +436"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 32,722"}]}
@@ -1,5 +1,5 @@
{
"cash": 29771205.096499994,
"cash": 24595928.921499994,
"initial": 100000000,
"positions": {
"138040": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 118,115 도달",
"cur_price": 115000,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 5467080.19828125,
"last_add_price": 120500
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 122,665 도달",
"cur_price": 128800,
"cur_price": 130800,
"tranches": 2,
"tranche_value": 5075960.404438956,
"last_add_price": 125200
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3902843.990735232,
"last_add_price": 136000
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3433240.983007488,
"last_add_price": 13420
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,947 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2017488.6720390161,
"last_add_price": 13940
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3513980.3038730407,
"last_add_price": 415500
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 35,709 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1369448.2358109318,
"last_add_price": 35800
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 163,
"entry_price": 31900,
"qty": 321,
"entry_price": 32318.380062305296,
"entry_at": "2026-08-24T13:12:22.279165+09:00",
"stop": 29900,
"target": 35900,
"peak": 32050,
"target": 37155,
"peak": 32800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 31,939 도달",
"cur_price": 31950,
"tranches": 1,
"cur_price": 32350,
"tranches": 2,
"tranche_value": 5207054.06571875,
"last_add_price": 31900
"last_add_price": 32750
}
},
"realized_pnl": -16466791.812500022,
@@ -197,5 +197,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-15T14:55:00.583198+09:00",
"updated_at": "2026-08-24T15:28:23.050672+09:00"
"updated_at": "2026-08-25T15:28:24.492793+09:00"
}
@@ -110,3 +110,4 @@
{"ts": "2026-08-20T09:16:22.852888+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 38, "cost": 1360604, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 23118, "target": 53964, "signals": [{"label": "손절선", "ok": true, "detail": "23,118 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 53,964"}, {"label": "추세(20일선)", "ok": true, "detail": "26,208"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T13:12:22.278671+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 625, "proceeds": 3137620, "entry_price": 6333, "pnl": -821064, "pnl_pct": -20.57, "hold_from": "2026-07-30T09:04:07.515982+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 8,921"}, {"label": "추세(20일선)", "ok": true, "detail": "4,754"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:22.279168+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 31900, "qty": 163, "cost": 5200480, "path": "pullback", "reason": "눌림목 지정가 31,939 도달", "stop": 29900, "target": 35900, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "31,850 vs 31,405"}, {"label": "정배열(5>20)", "ok": true, "detail": "32,390 / 31,405"}, {"label": "추세 기울기(20일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "32,390 / 30,876"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+3.6%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -892 · 기 +171 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+37%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.24% (환산) vs 평균 0.34% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 33,850"}, {"label": "거래량 급증", "ok": false, "detail": "1,768,231 (환산) vs 평균 2,489,243"}, {"label": "회전율 급증", "ok": false, "detail": "0.24% (환산) vs 평균 0.34%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 31,939 (현재 31,850)"}]}
{"ts": "2026-08-25T11:36:19.776873+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 32750, "qty": 158, "cost": 5175276, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 29900, "target": 35900, "signals": [{"label": "손절선", "ok": true, "detail": "29,900 (현재 32,750)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 35,900"}, {"label": "추세(20일선)", "ok": true, "detail": "31,450"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -1,322 · 기 +436"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 32,722"}]}
@@ -1,5 +1,5 @@
{
"cash": 38800060.64600003,
"cash": 33746802.77100003,
"initial": 100000000,
"positions": {
"010950": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3832048.06532556,
"last_add_price": 136000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3393597.375168939,
"last_add_price": 13420
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,170 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2024244.4253400748,
"last_add_price": 13830
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4815698.713054757,
"last_add_price": 196400
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3472153.1669509606,
"last_add_price": 415500
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1360762.8418676818,
"last_add_price": 35800
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 147,729 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1555265.8091770594,
"last_add_price": 144900
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 44,
"entry_price": 114500,
"qty": 87,
"entry_price": 115982.75862068965,
"entry_at": "2026-08-24T13:12:22.284078+09:00",
"stop": 111200,
"target": 121100,
"peak": 115500,
"target": 125548,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,169 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 5139578.521625002,
"last_add_price": 114500
"last_add_price": 117500
}
},
"realized_pnl": -17517547.978000022,
@@ -197,5 +197,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-15T14:55:00.583212+09:00",
"updated_at": "2026-08-24T15:28:23.055048+09:00"
"updated_at": "2026-08-25T15:28:24.497214+09:00"
}
@@ -111,3 +111,4 @@
{"ts": "2026-08-20T09:16:22.857442+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 38, "cost": 1360604, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 23118, "target": 53964, "signals": [{"label": "손절선", "ok": true, "detail": "23,118 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 53,964"}, {"label": "추세(20일선)", "ok": true, "detail": "26,208"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T13:12:22.283801+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 617, "proceeds": 3097458, "entry_price": 6333, "pnl": -810498, "pnl_pct": -20.57, "hold_from": "2026-07-30T09:04:07.518900+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 8,921"}, {"label": "추세(20일선)", "ok": true, "detail": "4,754"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:22.284081+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 114500, "qty": 44, "cost": 5038756, "path": "pullback", "reason": "눌림목 지정가 115,169 도달", "stop": 111200, "target": 121100, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "114,200 vs 113,265"}, {"label": "정배열(5>20)", "ok": true, "detail": "116,300 / 113,265"}, {"label": "추세 기울기(20일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,300 / 109,922"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+13.5%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +48 · 기 -50 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+34%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.12% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "197,995 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.12% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "52"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,169 (현재 114,200)"}]}
{"ts": "2026-08-25T09:26:22.427060+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 117500, "qty": 43, "cost": 5053258, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 121100, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 117,300)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 121,100"}, {"label": "추세(20일선)", "ok": true, "detail": "113,420"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 117,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 26241131.8935,
"cash": 21131365.5435,
"initial": 100000000,
"positions": {
"138040": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 118,115 도달",
"cur_price": 115000,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 5327150.576000001,
"last_add_price": 120500
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 122,665 도달",
"cur_price": 128800,
"cur_price": 130800,
"tranches": 2,
"tranche_value": 4940372.790080874,
"last_add_price": 125200
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3791237.2526261737,
"last_add_price": 136000
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,947 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2020520.412274148,
"last_add_price": 13830
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4842897.369596543,
"last_add_price": 196400
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3465074.791945059,
"last_add_price": 415500
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 35,709 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1349451.2885879693,
"last_add_price": 35800
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 160,
"entry_price": 31900,
"qty": 316,
"entry_price": 32319.620253164558,
"entry_at": "2026-08-24T13:12:22.288565+09:00",
"stop": 29900,
"target": 35900,
"peak": 32050,
"target": 37159,
"peak": 32800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 31,939 도달",
"cur_price": 31950,
"tranches": 1,
"cur_price": 32350,
"tranches": 2,
"tranche_value": 5113659.21834375,
"last_add_price": 31900
"last_add_price": 32750
}
},
"realized_pnl": -18196294.97050002,
@@ -197,5 +197,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-15T14:55:00.583227+09:00",
"updated_at": "2026-08-24T15:28:23.059078+09:00"
"updated_at": "2026-08-25T15:28:24.501193+09:00"
}
@@ -103,3 +103,4 @@
{"ts": "2026-08-20T09:16:22.861461+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 37, "cost": 1324799, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 23118, "target": 53964, "signals": [{"label": "손절선", "ok": true, "detail": "23,118 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 53,964"}, {"label": "추세(10일선)", "ok": true, "detail": "28,922"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T13:12:22.288225+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 606, "proceeds": 3042236, "entry_price": 6333, "pnl": -796180, "pnl_pct": -20.58, "hold_from": "2026-07-30T09:04:07.522091+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 8,921"}, {"label": "추세(10일선)", "ok": false, "detail": "5,148"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:22.288567+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 31900, "qty": 160, "cost": 5104766, "path": "pullback", "reason": "눌림목 지정가 31,939 도달", "stop": 29900, "target": 35900, "signals": [{"label": "추세(10일선 위)", "ok": true, "detail": "31,850 vs 31,795"}, {"label": "정배열(5>10)", "ok": true, "detail": "32,390 / 31,795"}, {"label": "추세 기울기(10일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "32,390 / 30,876"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+3.6%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -892 · 기 +171 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+37%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.24% (환산) vs 평균 0.34% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 33,850"}, {"label": "거래량 급증", "ok": false, "detail": "1,768,231 (환산) vs 평균 2,489,243"}, {"label": "회전율 급증", "ok": false, "detail": "0.24% (환산) vs 평균 0.34%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 31,939 (현재 31,850)"}]}
{"ts": "2026-08-25T11:36:19.785923+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 32750, "qty": 156, "cost": 5109766, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 29900, "target": 35900, "signals": [{"label": "손절선", "ok": true, "detail": "29,900 (현재 32,750)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 35,900"}, {"label": "추세(10일선)", "ok": true, "detail": "31,885"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -1,322 · 기 +436"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 32,722"}]}
@@ -1,5 +1,5 @@
{
"cash": 34912164.782499954,
"cash": 28929102.83599996,
"initial": 100000000,
"positions": {
"078930": {
@@ -11,39 +11,18 @@
"qty": 100,
"entry_price": 91919.0,
"entry_at": "2026-08-04T09:50:10.235258+09:00",
"stop": 109994,
"stop": 111762,
"target": 104705,
"peak": 121000,
"peak": 123500,
"trailing_on": true,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 4659757.825201359,
"last_add_price": 93500
},
"252990": {
"code": "252990",
"name": "샘씨엔에스",
"sources": [
"auto"
],
"qty": 355,
"entry_price": 13422.760563380281,
"entry_at": "2026-08-05T09:02:14.777207+09:00",
"stop": 11093,
"target": 16917,
"peak": 16270,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,170 도달",
"cur_price": 13310,
"tranches": 2,
"tranche_value": 2390089.3312908146,
"last_add_price": 13830
},
"068270": {
"code": "068270",
"name": "셀트리온",
@@ -60,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 5316706.344343748,
"last_add_price": 196400
@@ -81,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 4726305.668282297,
"last_add_price": 400000
@@ -102,7 +81,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1835134.7132164796,
"last_add_price": 35800
@@ -123,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 147,729 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 2097577.151946268,
"last_add_price": 144900
@@ -144,7 +123,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 7,426 도달",
"cur_price": 5060,
"cur_price": 5040,
"tranches": 1,
"tranche_value": 2255592.7366090417,
"last_add_price": 5500
@@ -155,24 +134,45 @@
"sources": [
"auto"
],
"qty": 45,
"entry_price": 114300,
"qty": 89,
"entry_price": 115882.02247191011,
"entry_at": "2026-08-24T09:02:24.057753+09:00",
"stop": 111200,
"target": 118950,
"peak": 116200,
"target": 122905,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,212 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 5239664.769218747,
"last_add_price": 114300
"last_add_price": 117500
},
"030000": {
"code": "030000",
"name": "제일기획",
"sources": [
"auto"
],
"qty": 274,
"entry_price": 19190,
"entry_at": "2026-08-25T12:16:24.258018+09:00",
"stop": 18112,
"target": 20807,
"peak": 19250,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 19,672 도달",
"cur_price": 19110,
"tranches": 1,
"tranche_value": 5261803.221562497,
"last_add_price": 19190
}
},
"realized_pnl": -18572578.975500032,
"closed_trades": 48,
"realized_pnl": -18891811.47500003,
"closed_trades": 49,
"wins": 6,
"peak_equity": 100344662.275,
"max_drawdown": 0.17793081459150337,
@@ -192,12 +192,12 @@
"024060": "2026-07-27",
"003680": "2026-08-19",
"138040": "2026-08-04",
"252990": "2026-07-28",
"252990": "2026-08-25",
"090850": "2026-08-06",
"078930": "2026-07-31",
"003550": "2026-08-19",
"194370": "2026-08-24"
},
"created_at": "2026-06-15T14:55:00.583241+09:00",
"updated_at": "2026-08-24T15:28:23.062838+09:00"
"updated_at": "2026-08-25T15:28:24.505045+09:00"
}
@@ -129,3 +129,6 @@
{"ts": "2026-08-20T09:16:22.865424+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 51, "cost": 1826074, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 25688, "target": 44967, "signals": [{"label": "손절선", "ok": true, "detail": "25,688 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 44,967"}, {"label": "추세(20일선)", "ok": true, "detail": "26,208"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T09:00:26.545777+09:00", "side": "SELL", "code": "194370", "name": "제이에스코퍼레이션", "price": 12790, "qty": 698, "proceeds": 8910012, "entry_price": 13160, "pnl": -276966, "pnl_pct": -2.81, "hold_from": "2026-08-03T12:00:12.468905+09:00", "reason": "시간손절 — 20일 보유·진전 없음(자본 회전)", "partial": false, "signals": [{"label": "손절선", "ok": true, "detail": "11,874 (현재 12,750)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 15,089"}, {"label": "추세(20일선)", "ok": true, "detail": "11,913"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -59 · 기 -20"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T09:02:24.057756+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 114300, "qty": 45, "cost": 5144272, "path": "pullback", "reason": "눌림목 지정가 115,212 도달", "stop": 111200, "target": 118950, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "114,200 vs 113,265"}, {"label": "정배열(5>20)", "ok": true, "detail": "116,300 / 113,265"}, {"label": "추세 기울기(20일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,300 / 109,922"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+10.9%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +57 · 기 -54 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+34%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.02% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "30,233 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.02% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "52"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,212 (현재 114,200)"}]}
{"ts": "2026-08-25T09:04:20.846774+09:00", "side": "SELL", "code": "252990", "name": "샘씨엔에스", "price": 12550, "qty": 355, "proceeds": 4446562, "entry_price": 13423, "pnl": -319232, "pnl_pct": -6.5, "hold_from": "2026-08-05T09:02:14.777207+09:00", "reason": "시간손절 — 20일 보유·진전 없음(자본 회전)", "partial": false, "signals": [{"label": "손절선", "ok": true, "detail": "11,093 (현재 12,600)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 16,917"}, {"label": "추세(20일선)", "ok": false, "detail": "12,705"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +34 · 기 -30"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-25T09:26:22.435224+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 117500, "qty": 44, "cost": 5170776, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 118950, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 117,300)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 118,950"}, {"label": "추세(20일선)", "ok": true, "detail": "113,420"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 116,989"}]}
{"ts": "2026-08-25T12:16:24.258020+09:00", "side": "BUY", "code": "030000", "name": "제일기획", "price": 19190, "qty": 274, "cost": 5258849, "path": "pullback", "reason": "눌림목 지정가 19,672 도달", "stop": 18112, "target": 20807, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "19,180 vs 18,864"}, {"label": "정배열(5>20)", "ok": true, "detail": "19,622 / 18,864"}, {"label": "추세 기울기(20일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "19,622 / 19,362"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+0.8%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +274 · 기 -97 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+31%"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.21% (환산) vs 평균 0.62% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 21,150"}, {"label": "거래량 급증", "ok": false, "detail": "241,100 (환산) vs 평균 711,709"}, {"label": "회전율 급증", "ok": false, "detail": "0.21% (환산) vs 평균 0.62%"}, {"label": "RSI", "ok": true, "detail": "51"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 19,672 (현재 19,180)"}]}
@@ -1,5 +1,5 @@
{
"cash": 50170967.108500004,
"cash": 37906014.847,
"initial": 100000000,
"positions": {
"010950": {
@@ -18,32 +18,11 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 4011548.961969723,
"last_add_price": 136000
},
"252990": {
"code": "252990",
"name": "샘씨엔에스",
"sources": [
"auto"
],
"qty": 306,
"entry_price": 13511.437908496731,
"entry_at": "2026-08-05T09:08:12.255584+09:00",
"stop": 10405,
"target": 18170,
"peak": 16270,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,947 도달",
"cur_price": 13310,
"tranches": 2,
"tranche_value": 2074177.1346915166,
"last_add_price": 13940
},
"214450": {
"code": "214450",
"name": "파마리서치",
@@ -60,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3613600.002573951,
"last_add_price": 415500
@@ -81,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 35,709 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1405152.4684093075,
"last_add_price": 35800
@@ -102,7 +81,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 157,486 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1606000.2783602993,
"last_add_price": 144900
@@ -123,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 7,771 도달",
"cur_price": 5060,
"cur_price": 5040,
"tranches": 1,
"tranche_value": 1728362.8125607767,
"last_add_price": 5500
@@ -134,20 +113,20 @@
"sources": [
"auto"
],
"qty": 47,
"entry_price": 115000,
"qty": 92,
"entry_price": 116565.21739130435,
"entry_at": "2026-08-20T09:02:18.585107+09:00",
"stop": 111200,
"target": 120700,
"peak": 117500,
"target": 124613,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 117,922 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 5415388.14053125,
"last_add_price": 115000
"last_add_price": 118200
},
"316140": {
"code": "316140",
@@ -155,27 +134,48 @@
"sources": [
"auto"
],
"qty": 169,
"entry_price": 31550,
"qty": 334,
"entry_price": 31969.91017964072,
"entry_at": "2026-08-24T09:02:24.061441+09:00",
"stop": 29900,
"target": 34025,
"peak": 32050,
"target": 35075,
"peak": 32800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 31,946 도달",
"cur_price": 31950,
"tranches": 1,
"cur_price": 32350,
"tranches": 2,
"tranche_value": 5347393.5563125005,
"last_add_price": 31550
"last_add_price": 32400
},
"030000": {
"code": "030000",
"name": "제일기획",
"sources": [
"auto"
],
"qty": 279,
"entry_price": 19190,
"entry_at": "2026-08-25T12:16:24.261562+09:00",
"stop": 17760,
"target": 21335,
"peak": 19250,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 20,031 도달",
"cur_price": 19110,
"tranches": 1,
"tranche_value": 5366224.24678125,
"last_add_price": 19190
}
},
"realized_pnl": -14390367.889000028,
"closed_trades": 43,
"realized_pnl": -14769027.47400003,
"closed_trades": 44,
"wins": 6,
"peak_equity": 101368884.75700004,
"max_drawdown": 0.16179288869376138,
"max_drawdown": 0.16324454094733756,
"last_exit": {
"030000": "2026-08-13",
"086790": "2026-08-19",
@@ -189,7 +189,7 @@
"319660": "2026-07-23",
"003490": "2026-07-09",
"105560": "2026-07-20",
"252990": "2026-07-28",
"252990": "2026-08-25",
"003680": "2026-08-19",
"010950": "2026-07-29",
"090850": "2026-08-05",
@@ -199,5 +199,5 @@
"194370": "2026-08-24"
},
"created_at": "2026-06-15T14:55:00.583273+09:00",
"updated_at": "2026-08-24T15:28:23.066282+09:00"
"updated_at": "2026-08-25T15:28:24.508526+09:00"
}
@@ -121,3 +121,7 @@
{"ts": "2026-08-20T09:16:22.868997+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 39, "cost": 1396409, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 23118, "target": 48823, "signals": [{"label": "손절선", "ok": true, "detail": "23,118 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 48,823"}, {"label": "추세(20일선)", "ok": true, "detail": "26,208"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T09:00:26.646312+09:00", "side": "SELL", "code": "194370", "name": "제이에스코퍼레이션", "price": 12790, "qty": 535, "proceeds": 6829307, "entry_price": 13160, "pnl": -212219, "pnl_pct": -2.81, "hold_from": "2026-08-03T12:00:12.472627+09:00", "reason": "시간손절 — 20일 보유·진전 없음(자본 회전)", "partial": false, "signals": [{"label": "손절선", "ok": true, "detail": "11,445 (현재 12,750)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 15,732"}, {"label": "추세(20일선)", "ok": true, "detail": "11,913"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -59 · 기 -20"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T09:02:24.061444+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 31550, "qty": 169, "cost": 5332750, "path": "pullback", "reason": "눌림목 지정가 31,946 도달", "stop": 29900, "target": 34025, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "31,500 vs 31,388"}, {"label": "정배열(5>20)", "ok": true, "detail": "32,320 / 31,388"}, {"label": "추세 기울기(20일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "32,320 / 30,870"}, {"label": "상대강도(시장대비)", "ok": false, "detail": "-0.2%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -988 · 기 +144 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+39%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.08% (환산) vs 평균 0.34% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 33,850"}, {"label": "거래량 급증", "ok": false, "detail": "551,940 (환산) vs 평균 2,489,243"}, {"label": "회전율 급증", "ok": false, "detail": "0.08% (환산) vs 평균 0.34%"}, {"label": "RSI", "ok": true, "detail": "51"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 31,946 (현재 31,500)"}]}
{"ts": "2026-08-25T09:10:22.337593+09:00", "side": "SELL", "code": "252990", "name": "샘씨엔에스", "price": 12300, "qty": 306, "proceeds": 3756461, "entry_price": 13511, "pnl": -378660, "pnl_pct": -8.97, "hold_from": "2026-08-05T09:08:12.255584+09:00", "reason": "시간손절 — 20일 보유·진전 없음(자본 회전)", "partial": false, "signals": [{"label": "손절선", "ok": true, "detail": "10,405 (현재 12,330)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 18,170"}, {"label": "추세(20일선)", "ok": false, "detail": "12,692"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +34 · 기 -30"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-25T09:34:19.115760+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 45, "cost": 5319798, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 120700, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 120,700"}, {"label": "추세(20일선)", "ok": true, "detail": "113,465"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 117,689"}]}
{"ts": "2026-08-25T09:46:21.219302+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 32400, "qty": 165, "cost": 5346802, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 29900, "target": 34025, "signals": [{"label": "손절선", "ok": true, "detail": "29,900 (현재 32,400)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 34,025"}, {"label": "추세(20일선)", "ok": true, "detail": "31,432"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -1,322 · 기 +436"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 32,372"}]}
{"ts": "2026-08-25T12:16:24.261564+09:00", "side": "BUY", "code": "030000", "name": "제일기획", "price": 19190, "qty": 279, "cost": 5354813, "path": "pullback", "reason": "눌림목 지정가 20,031 도달", "stop": 17760, "target": 21335, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "19,180 vs 18,864"}, {"label": "정배열(5>20)", "ok": true, "detail": "19,622 / 18,864"}, {"label": "추세 기울기(20일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "19,622 / 19,362"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+0.8%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +274 · 기 -97 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+31%"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.21% (환산) vs 평균 0.62% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 21,150"}, {"label": "거래량 급증", "ok": false, "detail": "241,100 (환산) vs 평균 711,709"}, {"label": "회전율 급증", "ok": false, "detail": "0.21% (환산) vs 평균 0.62%"}, {"label": "RSI", "ok": true, "detail": "51"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 20,031 (현재 19,180)"}]}
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3813556.4512976124,
"last_add_price": 136000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3377559.3310465557,
"last_add_price": 13420
@@ -53,14 +53,14 @@
"qty": 73,
"entry_price": 91928.76712328767,
"entry_at": "2026-08-04T09:50:10.241980+09:00",
"stop": 109994,
"stop": 111762,
"target": 114659,
"peak": 121000,
"peak": 123500,
"trailing_on": true,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3429919.977026871,
"last_add_price": 93500
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,170 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2016864.6660954827,
"last_add_price": 13830
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4837813.664610954,
"last_add_price": 196400
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3481984.841611142,
"last_add_price": 416500
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1387817.2573049513,
"last_add_price": 35800
@@ -160,12 +160,12 @@
"entry_at": "2026-08-24T13:14:22.571465+09:00",
"stop": 17760,
"target": 21480,
"peak": 19110,
"peak": 19250,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 19,655 도달",
"cur_price": 19100,
"cur_price": 19110,
"tranches": 1,
"tranche_value": 5237145.480593753,
"last_add_price": 19000
@@ -195,5 +195,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-15T14:55:00.583289+09:00",
"updated_at": "2026-08-24T15:28:23.069429+09:00"
"updated_at": "2026-08-25T15:28:24.511676+09:00"
}
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3863996.750727336,
"last_add_price": 136000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3428336.7554835076,
"last_add_price": 13420
@@ -53,14 +53,14 @@
"qty": 75,
"entry_price": 91929.33333333333,
"entry_at": "2026-08-04T09:50:10.245606+09:00",
"stop": 109994,
"stop": 111762,
"target": 108977,
"peak": 121000,
"peak": 123500,
"trailing_on": true,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3483887.723272795,
"last_add_price": 93500
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,170 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2048676.6433078528,
"last_add_price": 13830
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4916501.597175794,
"last_add_price": 196400
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3537977.413382886,
"last_add_price": 415500
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1411419.441084128,
"last_add_price": 35800
@@ -160,12 +160,12 @@
"entry_at": "2026-08-24T13:14:22.574664+09:00",
"stop": 17760,
"target": 20860,
"peak": 19110,
"peak": 19250,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 19,655 도달",
"cur_price": 19100,
"cur_price": 19110,
"tranches": 1,
"tranche_value": 5325511.789718751,
"last_add_price": 19000
@@ -195,5 +195,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-15T14:55:00.583304+09:00",
"updated_at": "2026-08-24T15:28:23.072558+09:00"
"updated_at": "2026-08-25T15:28:24.514810+09:00"
}
@@ -1,5 +1,5 @@
{
"cash": 26745606.15750003,
"cash": 21537575.07000003,
"initial": 100000000,
"positions": {
"138040": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 118,115 도달",
"cur_price": 115000,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 5436300.471625002,
"last_add_price": 120500
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 122,665 도달",
"cur_price": 128800,
"cur_price": 130800,
"tranches": 2,
"tranche_value": 5045807.2398149045,
"last_add_price": 125200
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3860356.183815298,
"last_add_price": 136000
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,947 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2062154.0490506678,
"last_add_price": 13830
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4944712.1127089355,
"last_add_price": 196400
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3537997.7129504834,
"last_add_price": 415500
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 35,709 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1377959.1486430662,
"last_add_price": 35800
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 163,
"entry_price": 31900,
"qty": 322,
"entry_price": 32319.72049689441,
"entry_at": "2026-08-24T13:12:22.306023+09:00",
"stop": 29900,
"target": 34900,
"peak": 32050,
"target": 35949,
"peak": 32800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 31,939 도달",
"cur_price": 31950,
"tranches": 1,
"cur_price": 32350,
"tranches": 2,
"tranche_value": 5220757.257031252,
"last_add_price": 31900
"last_add_price": 32750
}
},
"realized_pnl": -16483989.55900002,
@@ -197,5 +197,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-15T14:55:00.583318+09:00",
"updated_at": "2026-08-24T15:28:23.075550+09:00"
"updated_at": "2026-08-25T15:28:24.517827+09:00"
}
@@ -111,3 +111,4 @@
{"ts": "2026-08-20T09:16:22.878610+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 38, "cost": 1360604, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 23118, "target": 48823, "signals": [{"label": "손절선", "ok": true, "detail": "23,118 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 48,823"}, {"label": "추세(10일선)", "ok": true, "detail": "28,922"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T13:12:22.305766+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 619, "proceeds": 3107499, "entry_price": 6333, "pnl": -813139, "pnl_pct": -20.57, "hold_from": "2026-07-30T09:04:07.536435+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 8,274"}, {"label": "추세(10일선)", "ok": false, "detail": "5,148"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:22.306025+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 31900, "qty": 163, "cost": 5200480, "path": "pullback", "reason": "눌림목 지정가 31,939 도달", "stop": 29900, "target": 34900, "signals": [{"label": "추세(10일선 위)", "ok": true, "detail": "31,850 vs 31,795"}, {"label": "정배열(5>10)", "ok": true, "detail": "32,390 / 31,795"}, {"label": "추세 기울기(10일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "32,390 / 30,876"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+3.6%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -892 · 기 +171 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+37%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.24% (환산) vs 평균 0.34% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 33,850"}, {"label": "거래량 급증", "ok": false, "detail": "1,768,231 (환산) vs 평균 2,489,243"}, {"label": "회전율 급증", "ok": false, "detail": "0.24% (환산) vs 평균 0.34%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 31,939 (현재 31,850)"}]}
{"ts": "2026-08-25T11:36:19.803532+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 32750, "qty": 159, "cost": 5208031, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 29900, "target": 34900, "signals": [{"label": "손절선", "ok": true, "detail": "29,900 (현재 32,750)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 34,900"}, {"label": "추세(10일선)", "ok": true, "detail": "31,885"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -1,322 · 기 +436"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 32,722"}]}
@@ -1,28 +1,7 @@
{
"cash": 45018222.16959998,
"cash": 30209394.524599984,
"initial": 100000000,
"positions": {
"252990": {
"code": "252990",
"name": "샘씨엔에스",
"sources": [
"auto"
],
"qty": 338,
"entry_price": 13423.313609467456,
"entry_at": "2026-08-05T09:02:14.792964+09:00",
"stop": 11094,
"target": 18082,
"peak": 16270,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,947 도달",
"cur_price": 13310,
"tranches": 2,
"tranche_value": 2275843.933250867,
"last_add_price": 13830
},
"214450": {
"code": "214450",
"name": "파마리서치",
@@ -39,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 4532610.960524749,
"last_add_price": 416500
@@ -60,7 +39,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 35,709 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1761589.922015132,
"last_add_price": 35800
@@ -81,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 157,486 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 2013514.706883483,
"last_add_price": 144900
@@ -102,7 +81,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 7,771 도달",
"cur_price": 5060,
"cur_price": 5040,
"tranches": 1,
"tranche_value": 2166690.069603723,
"last_add_price": 5500
@@ -113,20 +92,20 @@
"sources": [
"auto"
],
"qty": 44,
"entry_price": 115000,
"qty": 87,
"entry_price": 116581.6091954023,
"entry_at": "2026-08-20T09:02:18.599361+09:00",
"stop": 111200,
"target": 122600,
"peak": 117500,
"target": 127345,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 117,922 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 5094447.6495062485,
"last_add_price": 115000
"last_add_price": 118200
},
"030000": {
"code": "030000",
@@ -134,20 +113,20 @@
"sources": [
"auto"
],
"qty": 270,
"entry_price": 18880,
"qty": 535,
"entry_price": 19058.317757009347,
"entry_at": "2026-08-20T09:08:18.031117+09:00",
"stop": 17770,
"target": 21100,
"peak": 19110,
"stop": 17980,
"target": 21214,
"peak": 19250,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 20,010 도달",
"cur_price": 19100,
"tranches": 1,
"cur_price": 19110,
"tranches": 2,
"tranche_value": 5113152.0870062485,
"last_add_price": 18880
"last_add_price": 19240
},
"316140": {
"code": "316140",
@@ -155,24 +134,45 @@
"sources": [
"auto"
],
"qty": 158,
"entry_price": 31550,
"qty": 312,
"entry_price": 31969.55128205128,
"entry_at": "2026-08-24T09:02:24.073710+09:00",
"stop": 29900,
"target": 34850,
"peak": 32050,
"target": 36109,
"peak": 32800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 31,946 도달",
"cur_price": 31950,
"tranches": 1,
"cur_price": 32350,
"tranches": 2,
"tranche_value": 5016181.869037499,
"last_add_price": 31550
"last_add_price": 32400
},
"161890": {
"code": "161890",
"name": "한국콜마",
"sources": [
"auto"
],
"qty": 26,
"entry_price": 148800,
"entry_at": "2026-08-25T12:16:24.309994+09:00",
"stop": 133669,
"target": 179062,
"peak": 150800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 149900,
"tranches": 1,
"tranche_value": 3964400.3679956635,
"last_add_price": 148800
}
},
"realized_pnl": -19162625.763400026,
"closed_trades": 52,
"realized_pnl": -19466758.030400027,
"closed_trades": 53,
"wins": 6,
"peak_equity": 101501376.42350003,
"max_drawdown": 0.22092032524603686,
@@ -190,7 +190,7 @@
"319660": "2026-07-23",
"105560": "2026-07-20",
"003680": "2026-08-19",
"252990": "2026-07-28",
"252990": "2026-08-25",
"010950": "2026-08-20",
"090850": "2026-08-05",
"161890": "2026-07-30",
@@ -200,5 +200,5 @@
"316140": "2026-08-21"
},
"created_at": "2026-06-15T14:55:00.583333+09:00",
"updated_at": "2026-08-24T15:28:23.078467+09:00"
"updated_at": "2026-08-25T15:28:24.520716+09:00"
}
@@ -145,3 +145,8 @@
{"ts": "2026-08-20T13:32:20.985167+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 31900, "qty": 159, "cost": 5072861, "path": "pullback", "reason": "눌림목 지정가 31,975 도달", "stop": 29900, "target": 35900, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "31,850 vs 31,405"}, {"label": "정배열(5>20)", "ok": true, "detail": "32,390 / 31,405"}, {"label": "추세 기울기(20일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "32,390 / 30,876"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+2.4%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +770 · 기 -588 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+37%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.39% (환산) vs 평균 0.34% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 33,850"}, {"label": "거래량 급증", "ok": false, "detail": "2,811,880 (환산) vs 평균 2,489,243"}, {"label": "회전율 급증", "ok": false, "detail": "0.39% (환산) vs 평균 0.34%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 31,975 (현재 31,850)"}]}
{"ts": "2026-08-21T09:02:19.788078+09:00", "side": "SELL", "code": "316140", "name": "우리금융지주", "price": 31350, "qty": 159, "proceeds": 4974930, "entry_price": 31900, "pnl": -97931, "pnl_pct": -1.72, "hold_from": "2026-08-20T13:32:20.985150+09:00", "reason": "추세·수급 이탈", "partial": false, "signals": [{"label": "손절선", "ok": true, "detail": "29,900 (현재 31,350)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 35,900"}, {"label": "추세(20일선)", "ok": false, "detail": "31,380"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -337 · 기 -91"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 32,741"}]}
{"ts": "2026-08-24T09:02:24.073712+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 31550, "qty": 158, "cost": 4985648, "path": "pullback", "reason": "눌림목 지정가 31,946 도달", "stop": 29900, "target": 34850, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "31,500 vs 31,388"}, {"label": "정배열(5>20)", "ok": true, "detail": "32,320 / 31,388"}, {"label": "추세 기울기(20일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "32,320 / 30,870"}, {"label": "상대강도(시장대비)", "ok": false, "detail": "-0.2%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -988 · 기 +144 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+39%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.08% (환산) vs 평균 0.34% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 33,850"}, {"label": "거래량 급증", "ok": false, "detail": "551,940 (환산) vs 평균 2,489,243"}, {"label": "회전율 급증", "ok": false, "detail": "0.08% (환산) vs 평균 0.34%"}, {"label": "RSI", "ok": true, "detail": "51"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 31,946 (현재 31,500)"}]}
{"ts": "2026-08-25T09:04:20.867256+09:00", "side": "SELL", "code": "252990", "name": "샘씨엔에스", "price": 12550, "qty": 338, "proceeds": 4233628, "entry_price": 13423, "pnl": -304132, "pnl_pct": -6.51, "hold_from": "2026-08-05T09:02:14.792964+09:00", "reason": "시간손절 — 20일 보유·진전 없음(자본 회전)", "partial": false, "signals": [{"label": "손절선", "ok": true, "detail": "11,094 (현재 12,600)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 18,082"}, {"label": "추세(20일선)", "ok": false, "detail": "12,705"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +34 · 기 -30"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-25T09:34:19.127950+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 43, "cost": 5083362, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 122600, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 122,600"}, {"label": "추세(20일선)", "ok": true, "detail": "113,465"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 117,689"}]}
{"ts": "2026-08-25T09:46:21.231619+09:00", "side": "BUY", "code": "316140", "name": "우리금융지주", "price": 32400, "qty": 154, "cost": 4990348, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 29900, "target": 34850, "signals": [{"label": "손절선", "ok": true, "detail": "29,900 (현재 32,400)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 34,850"}, {"label": "추세(20일선)", "ok": true, "detail": "31,432"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -1,322 · 기 +436"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 32,372"}]}
{"ts": "2026-08-25T12:16:24.309996+09:00", "side": "BUY", "code": "161890", "name": "한국콜마", "price": 148800, "qty": 26, "cost": 3869380, "path": "breakout", "reason": "거래량·회전율 동반 전고점 돌파", "stop": 133669, "target": 179062, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "148,600 vs 100,995"}, {"label": "정배열(5>20)", "ok": true, "detail": "117,020 / 100,995"}, {"label": "추세 기울기(20일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "117,020 / 92,743"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+73.7%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -153 · 기 +229 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+13%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "4.31% (환산) vs 평균 1.76% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": true, "detail": "고점 124,600"}, {"label": "거래량 급증", "ok": true, "detail": "1,015,655 (환산) vs 평균 415,172"}, {"label": "회전율 급증", "ok": true, "detail": "4.31% (환산) vs 평균 1.76%"}, {"label": "RSI", "ok": true, "detail": "80"}]}
{"ts": "2026-08-25T13:06:19.763129+09:00", "side": "BUY", "code": "030000", "name": "제일기획", "price": 19240, "qty": 265, "cost": 5099365, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 17770, "target": 21100, "signals": [{"label": "손절선", "ok": true, "detail": "17,770 (현재 19,240)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 21,100"}, {"label": "추세(20일선)", "ok": true, "detail": "18,866"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +274 · 기 -97"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 19,239"}]}
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 118,115 도달",
"cur_price": 115000,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 5234858.44571875,
"last_add_price": 120500
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 122,665 도달",
"cur_price": 128800,
"cur_price": 130800,
"tranches": 2,
"tranche_value": 5051285.121721717,
"last_add_price": 125200
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 4362596.463271157,
"last_add_price": 13420
@@ -74,14 +74,14 @@
"qty": 87,
"entry_price": 98831.03448275862,
"entry_at": "2026-08-04T14:12:15.953832+09:00",
"stop": 109994,
"stop": 111762,
"target": 117336,
"peak": 121000,
"peak": 123500,
"trailing_on": true,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 4360412.203364462,
"last_add_price": 100500
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,975 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 2357489.1026575607,
"last_add_price": 14290
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 4482545.509520558,
"last_add_price": 415500
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 35,709 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1748295.3872581045,
"last_add_price": 35800
@@ -165,7 +165,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 1,
"tranche_value": 5051512.08909375,
"last_add_price": 198700
@@ -197,5 +197,5 @@
"078930": "2026-07-31"
},
"created_at": "2026-06-15T14:55:00.583347+09:00",
"updated_at": "2026-08-24T15:28:23.081094+09:00"
"updated_at": "2026-08-25T15:28:24.523371+09:00"
}
@@ -1,5 +1,5 @@
{
"cash": 32827009.305499982,
"cash": 25486708.425499983,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3627364.7637924887,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3547525.853504033,
"last_add_price": 136000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 144,129 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 2,
"tranche_value": 1688085.214726109,
"last_add_price": 147600
@@ -76,12 +76,12 @@
"entry_at": "2026-08-04T09:34:11.170806+09:00",
"stop": 80334,
"target": 125794,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3165003.2713460657,
"last_add_price": 93500
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 104000,
"cur_price": 106000,
"tranches": 1,
"tranche_value": 3967368.72635773,
"last_add_price": 110000
@@ -118,12 +118,12 @@
"entry_at": "2026-08-20T09:18:26.020964+09:00",
"stop": 114576,
"target": 187273,
"peak": 143600,
"peak": 150800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 143300,
"cur_price": 149900,
"tranches": 2,
"tranche_value": 2862100.411366492,
"last_add_price": 135100
@@ -134,20 +134,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 115500,
"qty": 81,
"entry_price": 116833.33333333333,
"entry_at": "2026-08-24T09:10:26.855158+09:00",
"stop": 111200,
"target": 128400,
"peak": 116200,
"target": 133733,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,212 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4793046.880437499,
"last_add_price": 115500
"last_add_price": 118200
},
"257720": {
"code": "257720",
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 53,
"entry_price": 49450,
"qty": 104,
"entry_price": 50308.17307692308,
"entry_at": "2026-08-24T14:20:24.299052+09:00",
"stop": 42307,
"target": 70880,
"peak": 49650,
"stop": 43093,
"target": 71953,
"peak": 52000,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 49450,
"tranches": 1,
"cur_price": 51800,
"tranches": 2,
"tranche_value": 2646566.7841326115,
"last_add_price": 49450
"last_add_price": 51200
}
},
"realized_pnl": -26780682.757000055,
@@ -209,5 +209,5 @@
"252990": "2026-08-21"
},
"created_at": "2026-06-15T14:40:00.621011+09:00",
"updated_at": "2026-08-24T15:28:25.416702+09:00"
"updated_at": "2026-08-25T15:28:26.164503+09:00"
}
@@ -263,3 +263,5 @@
{"ts": "2026-08-24T13:14:23.815550+09:00", "side": "BUY", "code": "028260", "name": "삼성물산", "price": 362000, "qty": 13, "cost": 4706706, "path": "pullback", "reason": "눌림목 지정가 363,789 도달", "stop": 361999, "target": 362003, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "361,500 vs 350,292"}, {"label": "정배열(5>60)", "ok": true, "detail": "408,000 / 350,292"}, {"label": "추세 기울기(60일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "408,000 / 350,292"}, {"label": "상대강도(시장대비)", "ok": false, "detail": "-18.3%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -94 · 기 +139 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+37%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.41% (환산) vs 평균 0.43% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 565,000"}, {"label": "거래량 급증", "ok": false, "detail": "673,405 (환산) vs 평균 706,119"}, {"label": "회전율 급증", "ok": false, "detail": "0.41% (환산) vs 평균 0.43%"}, {"label": "RSI", "ok": true, "detail": "43"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 363,789 (현재 361,500)"}]}
{"ts": "2026-08-24T14:20:23.229101+09:00", "side": "SELL", "code": "028260", "name": "삼성물산", "price": 361000, "qty": 13, "proceeds": 4683849, "entry_price": 362000, "pnl": -22857, "pnl_pct": -0.28, "hold_from": "2026-08-24T13:14:23.815534+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "361,999 (현재 361,500)"}, {"label": "목표/트레일링", "ok": null, "detail": "트레일링 ON"}, {"label": "추세(60일선)", "ok": true, "detail": "350,292"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -88 · 기 +122"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T14:20:24.299069+09:00", "side": "BUY", "code": "257720", "name": "실리콘투", "price": 49450, "qty": 53, "cost": 2621243, "path": "breakout", "reason": "거래량·회전율 동반 전고점 돌파", "stop": 42307, "target": 70880, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "49,350 vs 36,263"}, {"label": "정배열(5>60)", "ok": true, "detail": "43,660 / 36,263"}, {"label": "추세 기울기(60일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "43,660 / 36,263"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+34.4%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -645 · 기 +1,162 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+17%"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "4.50% (환산) vs 평균 1.56% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": true, "detail": "고점 48,250"}, {"label": "거래량 급증", "ok": true, "detail": "2,948,495 (환산) vs 평균 1,023,767"}, {"label": "회전율 급증", "ok": true, "detail": "4.50% (환산) vs 평균 1.56%"}, {"label": "RSI", "ok": true, "detail": "69"}]}
{"ts": "2026-08-25T09:34:21.479342+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 40, "cost": 4728709, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 128400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 128,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,988"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,189"}]}
{"ts": "2026-08-25T09:56:20.664845+09:00", "side": "BUY", "code": "257720", "name": "실리콘투", "price": 51200, "qty": 51, "cost": 2611592, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 42307, "target": 70880, "signals": [{"label": "손절선", "ok": true, "detail": "42,307 (현재 51,300)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 70,880"}, {"label": "추세(60일선)", "ok": true, "detail": "36,296"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -428 · 기 +1,106"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 51,254"}]}
@@ -1,5 +1,5 @@
{
"cash": 28853204.581999995,
"cash": 24152499.581999995,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3627364.7637924887,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3470885.263866936,
"last_add_price": 136000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 144,129 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 2,
"tranche_value": 1668794.556510239,
"last_add_price": 147600
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3062184.227151159,
"last_add_price": 13420
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4403461.655606388,
"last_add_price": 198600
@@ -118,12 +118,12 @@
"entry_at": "2026-08-06T09:54:12.716178+09:00",
"stop": 87209,
"target": 137241,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3007474.6079847123,
"last_add_price": 101700
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1249060.2562777183,
"last_add_price": 35800
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 114500,
"qty": 81,
"entry_price": 115981.48148148147,
"entry_at": "2026-08-24T13:12:24.611989+09:00",
"stop": 111200,
"target": 124400,
"peak": 115500,
"target": 130326,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,169 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4719538.0473125,
"last_add_price": 114500
"last_add_price": 117500
}
},
"realized_pnl": -26369449.823500052,
@@ -211,5 +211,5 @@
"138040": "2026-08-19"
},
"created_at": "2026-06-15T14:55:00.583395+09:00",
"updated_at": "2026-08-24T15:28:25.426013+09:00"
"updated_at": "2026-08-25T15:28:26.175576+09:00"
}
@@ -242,3 +242,4 @@
{"ts": "2026-08-20T09:16:25.152513+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 34, "cost": 1217383, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 23118, "target": 64246, "signals": [{"label": "손절선", "ok": true, "detail": "23,118 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 64,246"}, {"label": "추세(60일선)", "ok": true, "detail": "31,336"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T13:12:24.611502+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 563, "proceeds": 2826368, "entry_price": 6333, "pnl": -739597, "pnl_pct": -20.57, "hold_from": "2026-07-30T09:04:08.152605+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 10,215"}, {"label": "추세(60일선)", "ok": true, "detail": "4,848"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:24.611992+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 114500, "qty": 41, "cost": 4695204, "path": "pullback", "reason": "눌림목 지정가 115,169 도달", "stop": 111200, "target": 124400, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "114,200 vs 109,922"}, {"label": "정배열(5>60)", "ok": true, "detail": "116,300 / 109,922"}, {"label": "추세 기울기(60일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,300 / 109,922"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+13.5%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +48 · 기 -50 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+34%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.12% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "197,995 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.12% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "52"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,169 (현재 114,200)"}]}
{"ts": "2026-08-25T09:26:24.836219+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 117500, "qty": 40, "cost": 4700705, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 124400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 117,300)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 124,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,973"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 117,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 36393346.89199998,
"cash": 31546419.961999983,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3570553.5376633056,
"last_add_price": 136000
@@ -55,12 +55,12 @@
"entry_at": "2026-08-04T09:34:11.191529+09:00",
"stop": 80360,
"target": 125820,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3188827.7982604695,
"last_add_price": 93500
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 104000,
"cur_price": 106000,
"tranches": 1,
"tranche_value": 3997085.960333706,
"last_add_price": 110000
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 7,424 도달",
"cur_price": 5060,
"cur_price": 5040,
"tranches": 1,
"tranche_value": 1576676.661387545,
"last_add_price": 5560
@@ -118,12 +118,12 @@
"entry_at": "2026-08-20T09:18:26.045143+09:00",
"stop": 114521,
"target": 187219,
"peak": 143600,
"peak": 150800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 143300,
"cur_price": 149900,
"tranches": 2,
"tranche_value": 2892213.1259382935,
"last_add_price": 135100
@@ -139,12 +139,12 @@
"entry_at": "2026-08-24T09:02:26.350420+09:00",
"stop": 100700,
"target": 247900,
"peak": 138100,
"peak": 139000,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 146,379 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1444957.780421195,
"last_add_price": 137500
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 42,
"entry_price": 115500,
"qty": 83,
"entry_price": 116833.73493975903,
"entry_at": "2026-08-24T09:10:26.877182+09:00",
"stop": 111200,
"target": 128400,
"peak": 116200,
"target": 133735,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,212 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4852688.408874999,
"last_add_price": 115500
"last_add_price": 118200
}
},
"realized_pnl": -25731292.656000044,
@@ -210,5 +210,5 @@
"252990": "2026-08-21"
},
"created_at": "2026-06-15T14:40:00.621059+09:00",
"updated_at": "2026-08-24T15:28:25.432244+09:00"
"updated_at": "2026-08-25T15:28:26.181998+09:00"
}
@@ -265,3 +265,4 @@
{"ts": "2026-08-24T09:04:22.497356+09:00", "side": "BUY", "code": "319660", "name": "피에스케이", "price": 125100, "qty": 38, "cost": 4754513, "path": "pullback", "reason": "눌림목 지정가 155,710 도달", "stop": 125099, "target": 125103, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "125,000 vs 112,642"}, {"label": "정배열(5>60)", "ok": true, "detail": "162,640 / 112,642"}, {"label": "추세 기울기(60일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "162,640 / 112,642"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+18.4%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +220 · 기 -194 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+54%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.69% (환산) vs 평균 2.68% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 196,500"}, {"label": "거래량 급증", "ok": false, "detail": "199,953 (환산) vs 평균 777,376"}, {"label": "회전율 급증", "ok": false, "detail": "0.69% (환산) vs 평균 2.68%"}, {"label": "RSI", "ok": true, "detail": "45"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 155,710 (현재 125,000)"}]}
{"ts": "2026-08-24T09:10:26.876426+09:00", "side": "SELL", "code": "319660", "name": "피에스케이", "price": 123300, "qty": 38, "proceeds": 4676263, "entry_price": 125100, "pnl": -78250, "pnl_pct": -1.44, "hold_from": "2026-08-24T09:04:22.497352+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "125,099 (현재 122,800)"}, {"label": "목표/트레일링", "ok": null, "detail": "트레일링 ON"}, {"label": "추세(60일선)", "ok": true, "detail": "112,605"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +220 · 기 -194"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T09:10:26.877186+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 115500, "qty": 42, "cost": 4851728, "path": "pullback", "reason": "눌림목 지정가 115,212 도달", "stop": 111200, "target": 128400, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "115,200 vs 109,938"}, {"label": "정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "추세 기울기(60일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+11.7%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +57 · 기 -54 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+33%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.04% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "61,480 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.04% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,212 (현재 115,200)"}]}
{"ts": "2026-08-25T09:34:21.504695+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 41, "cost": 4846927, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 128400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 128,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,988"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 35776524.87849997,
"cash": 31047815.67849997,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3511790.845106854,
"last_add_price": 136000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4454503.886577569,
"last_add_price": 198600
@@ -76,12 +76,12 @@
"entry_at": "2026-08-06T09:54:12.728482+09:00",
"stop": 87177,
"target": 137209,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3042388.163482306,
"last_add_price": 101700
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1267059.834384944,
"last_add_price": 35800
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 7,418 도달",
"cur_price": 5060,
"cur_price": 5040,
"tranches": 1,
"tranche_value": 1532132.3284042438,
"last_add_price": 5460
@@ -139,12 +139,12 @@
"entry_at": "2026-08-24T09:02:26.359805+09:00",
"stop": 100700,
"target": 247900,
"peak": 138100,
"peak": 139000,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 146,379 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1433848.3158772076,
"last_add_price": 137500
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 115500,
"qty": 81,
"entry_price": 116833.33333333333,
"entry_at": "2026-08-24T09:10:26.883582+09:00",
"stop": 111200,
"target": 128400,
"peak": 116200,
"target": 133733,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,212 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4806914.700218748,
"last_add_price": 115500
"last_add_price": 118200
}
},
"realized_pnl": -25478064.18150006,
@@ -213,5 +213,5 @@
"194370": "2026-08-24"
},
"created_at": "2026-06-15T14:55:00.583512+09:00",
"updated_at": "2026-08-24T15:28:25.438615+09:00"
"updated_at": "2026-08-25T15:28:26.188138+09:00"
}
@@ -259,3 +259,4 @@
{"ts": "2026-08-24T09:04:22.503213+09:00", "side": "BUY", "code": "319660", "name": "피에스케이", "price": 125100, "qty": 38, "cost": 4754513, "path": "pullback", "reason": "눌림목 지정가 155,710 도달", "stop": 125099, "target": 125103, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "125,000 vs 112,642"}, {"label": "정배열(5>60)", "ok": true, "detail": "162,640 / 112,642"}, {"label": "추세 기울기(60일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "162,640 / 112,642"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+18.4%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +220 · 기 -194 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+54%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.69% (환산) vs 평균 2.68% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 196,500"}, {"label": "거래량 급증", "ok": false, "detail": "199,953 (환산) vs 평균 777,376"}, {"label": "회전율 급증", "ok": false, "detail": "0.69% (환산) vs 평균 2.68%"}, {"label": "RSI", "ok": true, "detail": "45"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 155,710 (현재 125,000)"}]}
{"ts": "2026-08-24T09:10:26.883137+09:00", "side": "SELL", "code": "319660", "name": "피에스케이", "price": 123300, "qty": 38, "proceeds": 4676263, "entry_price": 125100, "pnl": -78250, "pnl_pct": -1.44, "hold_from": "2026-08-24T09:04:22.503211+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "125,099 (현재 122,800)"}, {"label": "목표/트레일링", "ok": null, "detail": "트레일링 ON"}, {"label": "추세(60일선)", "ok": true, "detail": "112,605"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +220 · 기 -194"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T09:10:26.883585+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 115500, "qty": 41, "cost": 4736210, "path": "pullback", "reason": "눌림목 지정가 115,212 도달", "stop": 111200, "target": 128400, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "115,200 vs 109,938"}, {"label": "정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "추세 기울기(60일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+11.7%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +57 · 기 -54 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+33%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.04% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "61,480 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.04% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,212 (현재 115,200)"}]}
{"ts": "2026-08-25T09:34:21.510487+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 40, "cost": 4728709, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 128400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 128,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,988"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 32815115.353499953,
"cash": 25474814.473499954,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3546894.320639113,
"last_add_price": 136000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 144,129 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 2,
"tranche_value": 1687768.019262798,
"last_add_price": 147600
@@ -76,12 +76,12 @@
"entry_at": "2026-08-04T09:34:11.202398+09:00",
"stop": 80334,
"target": 125794,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3164409.8348090188,
"last_add_price": 93500
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 104000,
"cur_price": 106000,
"tranches": 1,
"tranche_value": 3966630.036179657,
"last_add_price": 110000
@@ -118,12 +118,12 @@
"entry_at": "2026-08-20T09:18:26.056475+09:00",
"stop": 114576,
"target": 187273,
"peak": 143600,
"peak": 150800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 143300,
"cur_price": 149900,
"tranches": 2,
"tranche_value": 2861643.9610576206,
"last_add_price": 135100
@@ -134,20 +134,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 115500,
"qty": 81,
"entry_price": 116833.33333333333,
"entry_at": "2026-08-24T09:10:26.888722+09:00",
"stop": 111200,
"target": 128400,
"peak": 116200,
"target": 133733,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,212 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4792299.770406247,
"last_add_price": 115500
"last_add_price": 118200
},
"257720": {
"code": "257720",
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 53,
"entry_price": 49450,
"qty": 104,
"entry_price": 50308.17307692308,
"entry_at": "2026-08-24T14:20:24.329648+09:00",
"stop": 42307,
"target": 70880,
"peak": 49650,
"stop": 43093,
"target": 71953,
"peak": 52000,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 49450,
"tranches": 1,
"cur_price": 51800,
"tranches": 2,
"tranche_value": 2646155.083218149,
"last_add_price": 49450
"last_add_price": 51200
}
},
"realized_pnl": -26792576.70900005,
@@ -210,5 +210,5 @@
"252990": "2026-08-21"
},
"created_at": "2026-06-15T14:40:00.621103+09:00",
"updated_at": "2026-08-24T15:28:25.443658+09:00"
"updated_at": "2026-08-25T15:28:26.192715+09:00"
}
@@ -265,3 +265,5 @@
{"ts": "2026-08-24T13:14:24.912916+09:00", "side": "BUY", "code": "028260", "name": "삼성물산", "price": 362000, "qty": 13, "cost": 4706706, "path": "pullback", "reason": "눌림목 지정가 363,789 도달", "stop": 361999, "target": 362003, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "361,500 vs 350,292"}, {"label": "정배열(5>60)", "ok": true, "detail": "408,000 / 350,292"}, {"label": "추세 기울기(60일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "408,000 / 350,292"}, {"label": "상대강도(시장대비)", "ok": false, "detail": "-18.3%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -94 · 기 +139 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+37%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.41% (환산) vs 평균 0.43% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 565,000"}, {"label": "거래량 급증", "ok": false, "detail": "673,405 (환산) vs 평균 706,119"}, {"label": "회전율 급증", "ok": false, "detail": "0.41% (환산) vs 평균 0.43%"}, {"label": "RSI", "ok": true, "detail": "43"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 363,789 (현재 361,500)"}]}
{"ts": "2026-08-24T14:20:24.328546+09:00", "side": "SELL", "code": "028260", "name": "삼성물산", "price": 361000, "qty": 13, "proceeds": 4683849, "entry_price": 362000, "pnl": -22857, "pnl_pct": -0.28, "hold_from": "2026-08-24T13:14:24.912913+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "361,999 (현재 361,500)"}, {"label": "목표/트레일링", "ok": null, "detail": "트레일링 ON"}, {"label": "추세(60일선)", "ok": true, "detail": "350,292"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -88 · 기 +122"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T14:20:24.329651+09:00", "side": "BUY", "code": "257720", "name": "실리콘투", "price": 49450, "qty": 53, "cost": 2621243, "path": "breakout", "reason": "거래량·회전율 동반 전고점 돌파", "stop": 42307, "target": 70880, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "49,350 vs 36,263"}, {"label": "정배열(5>60)", "ok": true, "detail": "43,660 / 36,263"}, {"label": "추세 기울기(60일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "43,660 / 36,263"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+34.4%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -645 · 기 +1,162 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+17%"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "4.50% (환산) vs 평균 1.56% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": true, "detail": "고점 48,250"}, {"label": "거래량 급증", "ok": true, "detail": "2,948,495 (환산) vs 평균 1,023,767"}, {"label": "회전율 급증", "ok": true, "detail": "4.50% (환산) vs 평균 1.56%"}, {"label": "RSI", "ok": true, "detail": "69"}]}
{"ts": "2026-08-25T09:34:21.516054+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 40, "cost": 4728709, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 128400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 128,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,988"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,189"}]}
{"ts": "2026-08-25T09:56:20.697239+09:00", "side": "BUY", "code": "257720", "name": "실리콘투", "price": 51200, "qty": 51, "cost": 2611592, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 42307, "target": 70880, "signals": [{"label": "손절선", "ok": true, "detail": "42,307 (현재 51,300)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 70,880"}, {"label": "추세(60일선)", "ok": true, "detail": "36,296"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -428 · 기 +1,106"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 51,254"}]}
@@ -1,5 +1,5 @@
{
"cash": 28820210.76549997,
"cash": 24119505.76549997,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3468191.0592600796,
"last_add_price": 136000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 144,129 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 2,
"tranche_value": 1667469.2912320814,
"last_add_price": 147600
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3059768.9052410396,
"last_add_price": 13420
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4399987.769056675,
"last_add_price": 198600
@@ -118,12 +118,12 @@
"entry_at": "2026-08-06T09:54:12.737219+09:00",
"stop": 87209,
"target": 137241,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3005090.6555779856,
"last_add_price": 101700
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1248111.8252958565,
"last_add_price": 35800
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 114500,
"qty": 81,
"entry_price": 115981.48148148147,
"entry_at": "2026-08-24T13:12:24.633070+09:00",
"stop": 111200,
"target": 124400,
"peak": 115500,
"target": 130326,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,169 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4715894.6837812485,
"last_add_price": 114500
"last_add_price": 117500
}
},
"realized_pnl": -26428777.589500055,
@@ -212,5 +212,5 @@
"138040": "2026-08-19"
},
"created_at": "2026-06-15T14:55:00.583628+09:00",
"updated_at": "2026-08-24T15:28:25.448033+09:00"
"updated_at": "2026-08-25T15:28:26.197334+09:00"
}
@@ -244,3 +244,4 @@
{"ts": "2026-08-20T09:16:25.175789+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 34, "cost": 1217383, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 23118, "target": 64246, "signals": [{"label": "손절선", "ok": true, "detail": "23,118 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 64,246"}, {"label": "추세(60일선)", "ok": true, "detail": "31,336"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T13:12:24.632854+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 563, "proceeds": 2826368, "entry_price": 6333, "pnl": -739597, "pnl_pct": -20.57, "hold_from": "2026-07-30T09:04:08.173280+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 10,215"}, {"label": "추세(60일선)", "ok": true, "detail": "4,848"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:24.633073+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 114500, "qty": 41, "cost": 4695204, "path": "pullback", "reason": "눌림목 지정가 115,169 도달", "stop": 111200, "target": 124400, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "114,200 vs 109,922"}, {"label": "정배열(5>60)", "ok": true, "detail": "116,300 / 109,922"}, {"label": "추세 기울기(60일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,300 / 109,922"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+13.5%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +48 · 기 -50 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+34%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.12% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "197,995 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.12% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "52"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,169 (현재 114,200)"}]}
{"ts": "2026-08-25T09:26:24.859608+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 117500, "qty": 40, "cost": 4700705, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 124400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 117,300)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 124,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,973"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 117,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 36393346.89199998,
"cash": 31546419.961999983,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3570553.5376633056,
"last_add_price": 136000
@@ -55,12 +55,12 @@
"entry_at": "2026-08-04T09:34:11.210973+09:00",
"stop": 80360,
"target": 125820,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3188827.7982604695,
"last_add_price": 93500
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 104000,
"cur_price": 106000,
"tranches": 1,
"tranche_value": 3997085.960333706,
"last_add_price": 110000
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 7,424 도달",
"cur_price": 5060,
"cur_price": 5040,
"tranches": 1,
"tranche_value": 1576676.661387545,
"last_add_price": 5560
@@ -118,12 +118,12 @@
"entry_at": "2026-08-20T09:18:26.065190+09:00",
"stop": 114521,
"target": 187219,
"peak": 143600,
"peak": 150800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 143300,
"cur_price": 149900,
"tranches": 2,
"tranche_value": 2892213.1259382935,
"last_add_price": 135100
@@ -139,12 +139,12 @@
"entry_at": "2026-08-24T09:02:26.376579+09:00",
"stop": 100700,
"target": 247900,
"peak": 138100,
"peak": 139000,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 146,379 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1444957.780421195,
"last_add_price": 137500
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 42,
"entry_price": 115500,
"qty": 83,
"entry_price": 116833.73493975903,
"entry_at": "2026-08-24T09:10:26.897215+09:00",
"stop": 111200,
"target": 128400,
"peak": 116200,
"target": 133735,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,212 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4852688.408874999,
"last_add_price": 115500
"last_add_price": 118200
}
},
"realized_pnl": -25731292.656000044,
@@ -210,5 +210,5 @@
"252990": "2026-08-21"
},
"created_at": "2026-06-15T14:40:00.621148+09:00",
"updated_at": "2026-08-24T15:28:25.451894+09:00"
"updated_at": "2026-08-25T15:28:26.201116+09:00"
}
@@ -265,3 +265,4 @@
{"ts": "2026-08-24T09:04:22.515888+09:00", "side": "BUY", "code": "319660", "name": "피에스케이", "price": 125100, "qty": 38, "cost": 4754513, "path": "pullback", "reason": "눌림목 지정가 155,710 도달", "stop": 125099, "target": 125103, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "125,000 vs 112,642"}, {"label": "정배열(5>60)", "ok": true, "detail": "162,640 / 112,642"}, {"label": "추세 기울기(60일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "162,640 / 112,642"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+18.4%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +220 · 기 -194 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+54%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.69% (환산) vs 평균 2.68% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 196,500"}, {"label": "거래량 급증", "ok": false, "detail": "199,953 (환산) vs 평균 777,376"}, {"label": "회전율 급증", "ok": false, "detail": "0.69% (환산) vs 평균 2.68%"}, {"label": "RSI", "ok": true, "detail": "45"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 155,710 (현재 125,000)"}]}
{"ts": "2026-08-24T09:10:26.896933+09:00", "side": "SELL", "code": "319660", "name": "피에스케이", "price": 123300, "qty": 38, "proceeds": 4676263, "entry_price": 125100, "pnl": -78250, "pnl_pct": -1.44, "hold_from": "2026-08-24T09:04:22.515886+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "125,099 (현재 122,800)"}, {"label": "목표/트레일링", "ok": null, "detail": "트레일링 ON"}, {"label": "추세(60일선)", "ok": true, "detail": "112,605"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +220 · 기 -194"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T09:10:26.897218+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 115500, "qty": 42, "cost": 4851728, "path": "pullback", "reason": "눌림목 지정가 115,212 도달", "stop": 111200, "target": 128400, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "115,200 vs 109,938"}, {"label": "정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "추세 기울기(60일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+11.7%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +57 · 기 -54 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+33%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.04% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "61,480 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.04% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,212 (현재 115,200)"}]}
{"ts": "2026-08-25T09:34:21.524742+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 41, "cost": 4846927, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 128400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 128,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,988"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 35776524.87849997,
"cash": 31047815.67849997,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3511790.845106854,
"last_add_price": 136000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4454503.886577569,
"last_add_price": 198600
@@ -76,12 +76,12 @@
"entry_at": "2026-08-06T09:54:12.744635+09:00",
"stop": 87177,
"target": 137209,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3042388.163482306,
"last_add_price": 101700
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1267059.834384944,
"last_add_price": 35800
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 7,418 도달",
"cur_price": 5060,
"cur_price": 5040,
"tranches": 1,
"tranche_value": 1532132.3284042438,
"last_add_price": 5460
@@ -139,12 +139,12 @@
"entry_at": "2026-08-24T09:02:26.381162+09:00",
"stop": 100700,
"target": 247900,
"peak": 138100,
"peak": 139000,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 146,379 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1433848.3158772076,
"last_add_price": 137500
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 115500,
"qty": 81,
"entry_price": 116833.33333333333,
"entry_at": "2026-08-24T09:10:26.900987+09:00",
"stop": 111200,
"target": 128400,
"peak": 116200,
"target": 133733,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,212 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4806914.700218748,
"last_add_price": 115500
"last_add_price": 118200
}
},
"realized_pnl": -25478064.18150006,
@@ -213,5 +213,5 @@
"194370": "2026-08-24"
},
"created_at": "2026-06-15T14:55:00.583751+09:00",
"updated_at": "2026-08-24T15:28:25.455797+09:00"
"updated_at": "2026-08-25T15:28:26.205159+09:00"
}
@@ -259,3 +259,4 @@
{"ts": "2026-08-24T09:04:22.519513+09:00", "side": "BUY", "code": "319660", "name": "피에스케이", "price": 125100, "qty": 38, "cost": 4754513, "path": "pullback", "reason": "눌림목 지정가 155,710 도달", "stop": 125099, "target": 125103, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "125,000 vs 112,642"}, {"label": "정배열(5>60)", "ok": true, "detail": "162,640 / 112,642"}, {"label": "추세 기울기(60일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "162,640 / 112,642"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+18.4%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +220 · 기 -194 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+54%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.69% (환산) vs 평균 2.68% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 196,500"}, {"label": "거래량 급증", "ok": false, "detail": "199,953 (환산) vs 평균 777,376"}, {"label": "회전율 급증", "ok": false, "detail": "0.69% (환산) vs 평균 2.68%"}, {"label": "RSI", "ok": true, "detail": "45"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 155,710 (현재 125,000)"}]}
{"ts": "2026-08-24T09:10:26.900690+09:00", "side": "SELL", "code": "319660", "name": "피에스케이", "price": 123300, "qty": 38, "proceeds": 4676263, "entry_price": 125100, "pnl": -78250, "pnl_pct": -1.44, "hold_from": "2026-08-24T09:04:22.519511+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "125,099 (현재 122,800)"}, {"label": "목표/트레일링", "ok": null, "detail": "트레일링 ON"}, {"label": "추세(60일선)", "ok": true, "detail": "112,605"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +220 · 기 -194"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T09:10:26.900990+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 115500, "qty": 41, "cost": 4736210, "path": "pullback", "reason": "눌림목 지정가 115,212 도달", "stop": 111200, "target": 128400, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "115,200 vs 109,938"}, {"label": "정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "추세 기울기(60일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+11.7%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +57 · 기 -54 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+33%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.04% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "61,480 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.04% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,212 (현재 115,200)"}]}
{"ts": "2026-08-25T09:34:21.528690+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 40, "cost": 4728709, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 128400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 128,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,988"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 33214270.101999976,
"cash": 28485560.901999976,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3570553.5376633056,
"last_add_price": 136000
@@ -55,12 +55,12 @@
"entry_at": "2026-08-04T09:34:11.218304+09:00",
"stop": 80360,
"target": 125820,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3188827.7982604695,
"last_add_price": 93500
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 104000,
"cur_price": 106000,
"tranches": 1,
"tranche_value": 3997085.960333706,
"last_add_price": 110000
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 7,424 도달",
"cur_price": 5060,
"cur_price": 5040,
"tranches": 1,
"tranche_value": 1576676.661387545,
"last_add_price": 5560
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 455500,
"cur_price": 450000,
"tranches": 2,
"tranche_value": 4892847.491999999,
"last_add_price": 454500
@@ -139,12 +139,12 @@
"entry_at": "2026-08-24T09:02:26.385356+09:00",
"stop": 100700,
"target": 247900,
"peak": 138100,
"peak": 139000,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 146,379 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1437632.3116457197,
"last_add_price": 137500
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 115500,
"qty": 81,
"entry_price": 116833.33333333333,
"entry_at": "2026-08-24T09:10:26.904727+09:00",
"stop": 111200,
"target": 128400,
"peak": 116200,
"target": 133733,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,212 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4827275.136062499,
"last_add_price": 115500
"last_add_price": 118200
}
},
"realized_pnl": -25731292.656000044,
@@ -210,5 +210,5 @@
"252990": "2026-08-21"
},
"created_at": "2026-06-15T14:40:00.621367+09:00",
"updated_at": "2026-08-24T15:28:25.459217+09:00"
"updated_at": "2026-08-25T15:28:26.208661+09:00"
}
@@ -265,3 +265,4 @@
{"ts": "2026-08-24T09:10:26.904453+09:00", "side": "SELL", "code": "319660", "name": "피에스케이", "price": 123300, "qty": 38, "proceeds": 4676263, "entry_price": 125100, "pnl": -78250, "pnl_pct": -1.44, "hold_from": "2026-08-24T09:04:22.522917+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "125,099 (현재 122,800)"}, {"label": "목표/트레일링", "ok": null, "detail": "트레일링 ON"}, {"label": "추세(60일선)", "ok": true, "detail": "112,605"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +220 · 기 -194"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T09:10:26.904729+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 115500, "qty": 41, "cost": 4736210, "path": "pullback", "reason": "눌림목 지정가 115,212 도달", "stop": 111200, "target": 128400, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "115,200 vs 109,938"}, {"label": "정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "추세 기울기(60일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+11.7%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +57 · 기 -54 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+33%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.04% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "61,480 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.04% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,212 (현재 115,200)"}]}
{"ts": "2026-08-24T14:56:24.306900+09:00", "side": "BUY", "code": "004370", "name": "농심", "price": 454500, "qty": 10, "cost": 4545682, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 411924, "target": 546227, "signals": [{"label": "손절선", "ok": true, "detail": "411,924 (현재 454,500)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 546,227"}, {"label": "추세(60일선)", "ok": true, "detail": "370,633"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +23 · 기 +24"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 454,037"}]}
{"ts": "2026-08-25T09:34:21.532431+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 40, "cost": 4728709, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 128400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 128,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,988"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 35776524.87849997,
"cash": 31047815.67849997,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3511790.845106854,
"last_add_price": 136000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4454503.886577569,
"last_add_price": 198600
@@ -76,12 +76,12 @@
"entry_at": "2026-08-06T09:54:12.751274+09:00",
"stop": 87177,
"target": 137209,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3042388.163482306,
"last_add_price": 101700
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1267059.834384944,
"last_add_price": 35800
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 7,418 도달",
"cur_price": 5060,
"cur_price": 5040,
"tranches": 1,
"tranche_value": 1532132.3284042438,
"last_add_price": 5460
@@ -139,12 +139,12 @@
"entry_at": "2026-08-24T09:02:26.389383+09:00",
"stop": 100700,
"target": 247900,
"peak": 138100,
"peak": 139000,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 146,379 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1433848.3158772076,
"last_add_price": 137500
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 115500,
"qty": 81,
"entry_price": 116833.33333333333,
"entry_at": "2026-08-24T09:10:26.908095+09:00",
"stop": 111200,
"target": 128400,
"peak": 116200,
"target": 133733,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,212 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4806914.700218748,
"last_add_price": 115500
"last_add_price": 118200
}
},
"realized_pnl": -25478064.18150006,
@@ -213,5 +213,5 @@
"194370": "2026-08-24"
},
"created_at": "2026-06-15T14:55:00.584337+09:00",
"updated_at": "2026-08-24T15:28:25.462464+09:00"
"updated_at": "2026-08-25T15:28:26.212052+09:00"
}
@@ -259,3 +259,4 @@
{"ts": "2026-08-24T09:04:22.526222+09:00", "side": "BUY", "code": "319660", "name": "피에스케이", "price": 125100, "qty": 38, "cost": 4754513, "path": "pullback", "reason": "눌림목 지정가 155,710 도달", "stop": 125099, "target": 125103, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "125,000 vs 112,642"}, {"label": "정배열(5>60)", "ok": true, "detail": "162,640 / 112,642"}, {"label": "추세 기울기(60일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "162,640 / 112,642"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+18.4%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +220 · 기 -194 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+54%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.69% (환산) vs 평균 2.68% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 196,500"}, {"label": "거래량 급증", "ok": false, "detail": "199,953 (환산) vs 평균 777,376"}, {"label": "회전율 급증", "ok": false, "detail": "0.69% (환산) vs 평균 2.68%"}, {"label": "RSI", "ok": true, "detail": "45"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 155,710 (현재 125,000)"}]}
{"ts": "2026-08-24T09:10:26.907834+09:00", "side": "SELL", "code": "319660", "name": "피에스케이", "price": 123300, "qty": 38, "proceeds": 4676263, "entry_price": 125100, "pnl": -78250, "pnl_pct": -1.44, "hold_from": "2026-08-24T09:04:22.526221+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "125,099 (현재 122,800)"}, {"label": "목표/트레일링", "ok": null, "detail": "트레일링 ON"}, {"label": "추세(60일선)", "ok": true, "detail": "112,605"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +220 · 기 -194"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T09:10:26.908097+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 115500, "qty": 41, "cost": 4736210, "path": "pullback", "reason": "눌림목 지정가 115,212 도달", "stop": 111200, "target": 128400, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "115,200 vs 109,938"}, {"label": "정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "추세 기울기(60일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+11.7%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +57 · 기 -54 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+33%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.04% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "61,480 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.04% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,212 (현재 115,200)"}]}
{"ts": "2026-08-25T09:34:21.536178+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 40, "cost": 4728709, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 128400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 128,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,988"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 33214270.101999976,
"cash": 28485560.901999976,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3570553.5376633056,
"last_add_price": 136000
@@ -55,12 +55,12 @@
"entry_at": "2026-08-04T09:34:11.224674+09:00",
"stop": 80360,
"target": 125820,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3188827.7982604695,
"last_add_price": 93500
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 104000,
"cur_price": 106000,
"tranches": 1,
"tranche_value": 3997085.960333706,
"last_add_price": 110000
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 7,424 도달",
"cur_price": 5060,
"cur_price": 5040,
"tranches": 1,
"tranche_value": 1576676.661387545,
"last_add_price": 5560
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 455500,
"cur_price": 450000,
"tranches": 2,
"tranche_value": 4892847.491999999,
"last_add_price": 454500
@@ -139,12 +139,12 @@
"entry_at": "2026-08-24T09:02:26.392950+09:00",
"stop": 100700,
"target": 247900,
"peak": 138100,
"peak": 139000,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 146,379 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1437632.3116457197,
"last_add_price": 137500
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 115500,
"qty": 81,
"entry_price": 116833.33333333333,
"entry_at": "2026-08-24T09:10:26.911317+09:00",
"stop": 111200,
"target": 128400,
"peak": 116200,
"target": 133733,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,212 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4827275.136062499,
"last_add_price": 115500
"last_add_price": 118200
}
},
"realized_pnl": -25731292.656000044,
@@ -210,5 +210,5 @@
"252990": "2026-08-21"
},
"created_at": "2026-06-15T14:40:00.621411+09:00",
"updated_at": "2026-08-24T15:28:25.465537+09:00"
"updated_at": "2026-08-25T15:28:26.215070+09:00"
}
@@ -265,3 +265,4 @@
{"ts": "2026-08-24T09:10:26.911079+09:00", "side": "SELL", "code": "319660", "name": "피에스케이", "price": 123300, "qty": 38, "proceeds": 4676263, "entry_price": 125100, "pnl": -78250, "pnl_pct": -1.44, "hold_from": "2026-08-24T09:04:22.529501+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "125,099 (현재 122,800)"}, {"label": "목표/트레일링", "ok": null, "detail": "트레일링 ON"}, {"label": "추세(60일선)", "ok": true, "detail": "112,605"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +220 · 기 -194"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T09:10:26.911319+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 115500, "qty": 41, "cost": 4736210, "path": "pullback", "reason": "눌림목 지정가 115,212 도달", "stop": 111200, "target": 128400, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "115,200 vs 109,938"}, {"label": "정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "추세 기울기(60일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+11.7%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +57 · 기 -54 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+33%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.04% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "61,480 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.04% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,212 (현재 115,200)"}]}
{"ts": "2026-08-24T14:56:24.313261+09:00", "side": "BUY", "code": "004370", "name": "농심", "price": 454500, "qty": 10, "cost": 4545682, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 411924, "target": 546227, "signals": [{"label": "손절선", "ok": true, "detail": "411,924 (현재 454,500)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 546,227"}, {"label": "추세(60일선)", "ok": true, "detail": "370,633"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +23 · 기 +24"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 454,037"}]}
{"ts": "2026-08-25T09:34:21.539734+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 40, "cost": 4728709, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 128400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 128,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,988"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 35776524.87849997,
"cash": 31047815.67849997,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3511790.845106854,
"last_add_price": 136000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4454503.886577569,
"last_add_price": 198600
@@ -76,12 +76,12 @@
"entry_at": "2026-08-06T09:54:12.757233+09:00",
"stop": 87177,
"target": 137209,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3042388.163482306,
"last_add_price": 101700
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1267059.834384944,
"last_add_price": 35800
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 7,418 도달",
"cur_price": 5060,
"cur_price": 5040,
"tranches": 1,
"tranche_value": 1532132.3284042438,
"last_add_price": 5460
@@ -139,12 +139,12 @@
"entry_at": "2026-08-24T09:02:26.396294+09:00",
"stop": 100700,
"target": 247900,
"peak": 138100,
"peak": 139000,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 146,379 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1433848.3158772076,
"last_add_price": 137500
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 115500,
"qty": 81,
"entry_price": 116833.33333333333,
"entry_at": "2026-08-24T09:10:26.914383+09:00",
"stop": 111200,
"target": 128400,
"peak": 116200,
"target": 133733,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,212 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4806914.700218748,
"last_add_price": 115500
"last_add_price": 118200
}
},
"realized_pnl": -25478064.18150006,
@@ -213,5 +213,5 @@
"194370": "2026-08-24"
},
"created_at": "2026-06-15T14:55:00.584451+09:00",
"updated_at": "2026-08-24T15:28:25.468672+09:00"
"updated_at": "2026-08-25T15:28:26.218270+09:00"
}
@@ -259,3 +259,4 @@
{"ts": "2026-08-24T09:04:22.532506+09:00", "side": "BUY", "code": "319660", "name": "피에스케이", "price": 125100, "qty": 38, "cost": 4754513, "path": "pullback", "reason": "눌림목 지정가 155,710 도달", "stop": 125099, "target": 125103, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "125,000 vs 112,642"}, {"label": "정배열(5>60)", "ok": true, "detail": "162,640 / 112,642"}, {"label": "추세 기울기(60일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "162,640 / 112,642"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+18.4%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +220 · 기 -194 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+54%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.69% (환산) vs 평균 2.68% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 196,500"}, {"label": "거래량 급증", "ok": false, "detail": "199,953 (환산) vs 평균 777,376"}, {"label": "회전율 급증", "ok": false, "detail": "0.69% (환산) vs 평균 2.68%"}, {"label": "RSI", "ok": true, "detail": "45"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 155,710 (현재 125,000)"}]}
{"ts": "2026-08-24T09:10:26.914157+09:00", "side": "SELL", "code": "319660", "name": "피에스케이", "price": 123300, "qty": 38, "proceeds": 4676263, "entry_price": 125100, "pnl": -78250, "pnl_pct": -1.44, "hold_from": "2026-08-24T09:04:22.532505+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "125,099 (현재 122,800)"}, {"label": "목표/트레일링", "ok": null, "detail": "트레일링 ON"}, {"label": "추세(60일선)", "ok": true, "detail": "112,605"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +220 · 기 -194"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T09:10:26.914385+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 115500, "qty": 41, "cost": 4736210, "path": "pullback", "reason": "눌림목 지정가 115,212 도달", "stop": 111200, "target": 128400, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "115,200 vs 109,938"}, {"label": "정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "추세 기울기(60일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,500 / 109,938"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+11.7%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +57 · 기 -54 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+33%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.04% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "61,480 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.04% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "53"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,212 (현재 115,200)"}]}
{"ts": "2026-08-25T09:34:21.543108+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 40, "cost": 4728709, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 128400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 128,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,988"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 29390101.678499952,
"cash": 22049800.798499953,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3546894.320639113,
"last_add_price": 136000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 144,129 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 2,
"tranche_value": 1687768.019262798,
"last_add_price": 147600
@@ -76,12 +76,12 @@
"entry_at": "2026-08-04T09:34:11.230485+09:00",
"stop": 80334,
"target": 125794,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3164409.8348090188,
"last_add_price": 93500
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 104000,
"cur_price": 106000,
"tranches": 1,
"tranche_value": 3966630.036179657,
"last_add_price": 110000
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 455500,
"cur_price": 450000,
"tranches": 2,
"tranche_value": 4841386.2271249965,
"last_add_price": 454500
@@ -134,20 +134,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 115500,
"qty": 81,
"entry_price": 116833.33333333333,
"entry_at": "2026-08-24T09:10:26.917445+09:00",
"stop": 111200,
"target": 128400,
"peak": 116200,
"target": 133733,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,212 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4767416.525093747,
"last_add_price": 115500
"last_add_price": 118200
},
"257720": {
"code": "257720",
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 53,
"entry_price": 49450,
"qty": 104,
"entry_price": 50308.17307692308,
"entry_at": "2026-08-24T14:20:24.358532+09:00",
"stop": 42307,
"target": 70880,
"peak": 49650,
"stop": 43093,
"target": 71953,
"peak": 52000,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 49450,
"tranches": 1,
"cur_price": 51800,
"tranches": 2,
"tranche_value": 2633211.6885021683,
"last_add_price": 49450
"last_add_price": 51200
}
},
"realized_pnl": -26792576.70900005,
@@ -210,5 +210,5 @@
"252990": "2026-08-21"
},
"created_at": "2026-06-15T14:40:00.621453+09:00",
"updated_at": "2026-08-24T15:28:25.471448+09:00"
"updated_at": "2026-08-25T15:28:26.221057+09:00"
}
@@ -265,3 +265,5 @@
{"ts": "2026-08-24T14:20:24.357966+09:00", "side": "SELL", "code": "028260", "name": "삼성물산", "price": 361000, "qty": 13, "proceeds": 4683849, "entry_price": 362000, "pnl": -22857, "pnl_pct": -0.28, "hold_from": "2026-08-24T13:14:24.941038+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "361,999 (현재 361,500)"}, {"label": "목표/트레일링", "ok": null, "detail": "트레일링 ON"}, {"label": "추세(60일선)", "ok": true, "detail": "350,292"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -88 · 기 +122"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T14:20:24.358534+09:00", "side": "BUY", "code": "257720", "name": "실리콘투", "price": 49450, "qty": 53, "cost": 2621243, "path": "breakout", "reason": "거래량·회전율 동반 전고점 돌파", "stop": 42307, "target": 70880, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "49,350 vs 36,263"}, {"label": "정배열(5>60)", "ok": true, "detail": "43,660 / 36,263"}, {"label": "추세 기울기(60일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "43,660 / 36,263"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+34.4%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -645 · 기 +1,162 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+17%"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "4.50% (환산) vs 평균 1.56% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": true, "detail": "고점 48,250"}, {"label": "거래량 급증", "ok": true, "detail": "2,948,495 (환산) vs 평균 1,023,767"}, {"label": "회전율 급증", "ok": true, "detail": "4.50% (환산) vs 평균 1.56%"}, {"label": "RSI", "ok": true, "detail": "69"}]}
{"ts": "2026-08-24T14:56:24.319167+09:00", "side": "BUY", "code": "004370", "name": "농심", "price": 454500, "qty": 10, "cost": 4545682, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 411924, "target": 546227, "signals": [{"label": "손절선", "ok": true, "detail": "411,924 (현재 454,500)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 546,227"}, {"label": "추세(60일선)", "ok": true, "detail": "370,633"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +23 · 기 +24"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 454,037"}]}
{"ts": "2026-08-25T09:34:21.546145+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 40, "cost": 4728709, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 128400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 128,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,988"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,189"}]}
{"ts": "2026-08-25T09:56:20.725061+09:00", "side": "BUY", "code": "257720", "name": "실리콘투", "price": 51200, "qty": 51, "cost": 2611592, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 42307, "target": 70880, "signals": [{"label": "손절선", "ok": true, "detail": "42,307 (현재 51,300)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 70,880"}, {"label": "추세(60일선)", "ok": true, "detail": "36,296"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -428 · 기 +1,106"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 51,254"}]}
@@ -1,5 +1,5 @@
{
"cash": 28820210.76549997,
"cash": 24119505.76549997,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3468191.0592600796,
"last_add_price": 136000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 144,129 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 2,
"tranche_value": 1667469.2912320814,
"last_add_price": 147600
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3059768.9052410396,
"last_add_price": 13420
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4399987.769056675,
"last_add_price": 198600
@@ -118,12 +118,12 @@
"entry_at": "2026-08-06T09:54:12.762770+09:00",
"stop": 87209,
"target": 137241,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3005090.6555779856,
"last_add_price": 101700
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1248111.8252958565,
"last_add_price": 35800
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 114500,
"qty": 81,
"entry_price": 115981.48148148147,
"entry_at": "2026-08-24T13:12:24.658985+09:00",
"stop": 111200,
"target": 124400,
"peak": 115500,
"target": 130326,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,169 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4715894.6837812485,
"last_add_price": 114500
"last_add_price": 117500
}
},
"realized_pnl": -26428777.589500055,
@@ -212,5 +212,5 @@
"138040": "2026-08-19"
},
"created_at": "2026-06-15T14:55:00.584565+09:00",
"updated_at": "2026-08-24T15:28:25.474210+09:00"
"updated_at": "2026-08-25T15:28:26.223890+09:00"
}
@@ -244,3 +244,4 @@
{"ts": "2026-08-20T09:16:25.201665+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 34, "cost": 1217383, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 23118, "target": 64246, "signals": [{"label": "손절선", "ok": true, "detail": "23,118 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 64,246"}, {"label": "추세(60일선)", "ok": true, "detail": "31,336"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T13:12:24.658858+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 563, "proceeds": 2826368, "entry_price": 6333, "pnl": -739597, "pnl_pct": -20.57, "hold_from": "2026-07-30T09:04:08.202607+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 10,215"}, {"label": "추세(60일선)", "ok": true, "detail": "4,848"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:24.658986+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 114500, "qty": 41, "cost": 4695204, "path": "pullback", "reason": "눌림목 지정가 115,169 도달", "stop": 111200, "target": 124400, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "114,200 vs 109,922"}, {"label": "정배열(5>60)", "ok": true, "detail": "116,300 / 109,922"}, {"label": "추세 기울기(60일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,300 / 109,922"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+13.5%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +48 · 기 -50 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+34%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.12% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "197,995 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.12% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "52"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,169 (현재 114,200)"}]}
{"ts": "2026-08-25T09:26:24.888764+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 117500, "qty": 40, "cost": 4700705, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 124400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 117,300)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 124,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,973"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 117,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 29390101.678499952,
"cash": 22049800.798499953,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3546894.320639113,
"last_add_price": 136000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 144,129 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 2,
"tranche_value": 1687768.019262798,
"last_add_price": 147600
@@ -76,12 +76,12 @@
"entry_at": "2026-08-04T09:34:11.235950+09:00",
"stop": 80334,
"target": 125794,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3164409.8348090188,
"last_add_price": 93500
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 104000,
"cur_price": 106000,
"tranches": 1,
"tranche_value": 3966630.036179657,
"last_add_price": 110000
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 455500,
"cur_price": 450000,
"tranches": 2,
"tranche_value": 4841386.2271249965,
"last_add_price": 454500
@@ -134,20 +134,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 115500,
"qty": 81,
"entry_price": 116833.33333333333,
"entry_at": "2026-08-24T09:10:26.922902+09:00",
"stop": 111200,
"target": 128400,
"peak": 116200,
"target": 133733,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,212 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4767416.525093747,
"last_add_price": 115500
"last_add_price": 118200
},
"257720": {
"code": "257720",
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 53,
"entry_price": 49450,
"qty": 104,
"entry_price": 50308.17307692308,
"entry_at": "2026-08-24T14:20:24.364356+09:00",
"stop": 42307,
"target": 70880,
"peak": 49650,
"stop": 43093,
"target": 71953,
"peak": 52000,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 49450,
"tranches": 1,
"cur_price": 51800,
"tranches": 2,
"tranche_value": 2633211.6885021683,
"last_add_price": 49450
"last_add_price": 51200
}
},
"realized_pnl": -26792576.70900005,
@@ -210,5 +210,5 @@
"252990": "2026-08-21"
},
"created_at": "2026-06-15T14:40:00.621497+09:00",
"updated_at": "2026-08-24T15:28:25.476766+09:00"
"updated_at": "2026-08-25T15:28:26.226415+09:00"
}
@@ -265,3 +265,5 @@
{"ts": "2026-08-24T14:20:24.363800+09:00", "side": "SELL", "code": "028260", "name": "삼성물산", "price": 361000, "qty": 13, "proceeds": 4683849, "entry_price": 362000, "pnl": -22857, "pnl_pct": -0.28, "hold_from": "2026-08-24T13:14:24.946498+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "361,999 (현재 361,500)"}, {"label": "목표/트레일링", "ok": null, "detail": "트레일링 ON"}, {"label": "추세(60일선)", "ok": true, "detail": "350,292"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -88 · 기 +122"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T14:20:24.364357+09:00", "side": "BUY", "code": "257720", "name": "실리콘투", "price": 49450, "qty": 53, "cost": 2621243, "path": "breakout", "reason": "거래량·회전율 동반 전고점 돌파", "stop": 42307, "target": 70880, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "49,350 vs 36,263"}, {"label": "정배열(5>60)", "ok": true, "detail": "43,660 / 36,263"}, {"label": "추세 기울기(60일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "43,660 / 36,263"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+34.4%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -645 · 기 +1,162 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+17%"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "4.50% (환산) vs 평균 1.56% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": true, "detail": "고점 48,250"}, {"label": "거래량 급증", "ok": true, "detail": "2,948,495 (환산) vs 평균 1,023,767"}, {"label": "회전율 급증", "ok": true, "detail": "4.50% (환산) vs 평균 1.56%"}, {"label": "RSI", "ok": true, "detail": "69"}]}
{"ts": "2026-08-24T14:56:24.324495+09:00", "side": "BUY", "code": "004370", "name": "농심", "price": 454500, "qty": 10, "cost": 4545682, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 411924, "target": 546227, "signals": [{"label": "손절선", "ok": true, "detail": "411,924 (현재 454,500)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 546,227"}, {"label": "추세(60일선)", "ok": true, "detail": "370,633"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +23 · 기 +24"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 454,037"}]}
{"ts": "2026-08-25T09:34:21.551758+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 40, "cost": 4728709, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 128400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 128,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,988"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,189"}]}
{"ts": "2026-08-25T09:56:20.730371+09:00", "side": "BUY", "code": "257720", "name": "실리콘투", "price": 51200, "qty": 51, "cost": 2611592, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 42307, "target": 70880, "signals": [{"label": "손절선", "ok": true, "detail": "42,307 (현재 51,300)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 70,880"}, {"label": "추세(60일선)", "ok": true, "detail": "36,296"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -428 · 기 +1,106"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 51,254"}]}
@@ -1,5 +1,5 @@
{
"cash": 28820210.76549997,
"cash": 24119505.76549997,
"initial": 100000000,
"positions": {
"214450": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3624744.3180141365,
"last_add_price": 356000
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3468191.0592600796,
"last_add_price": 136000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 144,129 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 2,
"tranche_value": 1667469.2912320814,
"last_add_price": 147600
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3059768.9052410396,
"last_add_price": 13420
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4399987.769056675,
"last_add_price": 198600
@@ -118,12 +118,12 @@
"entry_at": "2026-08-06T09:54:12.767767+09:00",
"stop": 87209,
"target": 137241,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3005090.6555779856,
"last_add_price": 101700
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,138 도달",
"cur_price": 34500,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 1248111.8252958565,
"last_add_price": 35800
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 41,
"entry_price": 114500,
"qty": 81,
"entry_price": 115981.48148148147,
"entry_at": "2026-08-24T13:12:24.664434+09:00",
"stop": 111200,
"target": 124400,
"peak": 115500,
"target": 130326,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,169 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4715894.6837812485,
"last_add_price": 114500
"last_add_price": 117500
}
},
"realized_pnl": -26428777.589500055,
@@ -212,5 +212,5 @@
"138040": "2026-08-19"
},
"created_at": "2026-06-15T14:55:00.584680+09:00",
"updated_at": "2026-08-24T15:28:25.479306+09:00"
"updated_at": "2026-08-25T15:28:26.229025+09:00"
}
@@ -244,3 +244,4 @@
{"ts": "2026-08-20T09:16:25.206940+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35800, "qty": 34, "cost": 1217383, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 23118, "target": 64246, "signals": [{"label": "손절선", "ok": true, "detail": "23,118 (현재 36,000)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 64,246"}, {"label": "추세(60일선)", "ok": true, "detail": "31,336"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -71 · 기 +7"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,971"}]}
{"ts": "2026-08-24T13:12:24.664301+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 563, "proceeds": 2826368, "entry_price": 6333, "pnl": -739597, "pnl_pct": -20.57, "hold_from": "2026-07-30T09:04:08.208371+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 10,215"}, {"label": "추세(60일선)", "ok": true, "detail": "4,848"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:24.664435+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 114500, "qty": 41, "cost": 4695204, "path": "pullback", "reason": "눌림목 지정가 115,169 도달", "stop": 111200, "target": 124400, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "114,200 vs 109,922"}, {"label": "정배열(5>60)", "ok": true, "detail": "116,300 / 109,922"}, {"label": "추세 기울기(60일선↑)", "ok": null, "detail": "게이트 끔(완화) · 실제 우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,300 / 109,922"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+13.5%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +48 · 기 -50 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+34%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.12% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "197,995 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.12% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "52"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,169 (현재 114,200)"}]}
{"ts": "2026-08-25T09:26:24.894551+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 117500, "qty": 40, "cost": 4700705, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 124400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 117,300)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 124,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,973"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 117,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 28780366.011999987,
"cash": 23962143.386999987,
"initial": 100000000,
"positions": {
"055550": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 104000,
"cur_price": 106000,
"tranches": 2,
"tranche_value": 4371332.168068898,
"last_add_price": 105800
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 3448434.606240806,
"last_add_price": 130800
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3417673.0117698065,
"last_add_price": 338000
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 128800,
"cur_price": 130800,
"tranches": 2,
"tranche_value": 4425860.654266699,
"last_add_price": 125200
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 228500,
"cur_price": 225500,
"tranches": 2,
"tranche_value": 2243002.5784807494,
"last_add_price": 244500
@@ -118,12 +118,12 @@
"entry_at": "2026-08-18T14:30:24.610130+09:00",
"stop": 113273,
"target": 185342,
"peak": 143600,
"peak": 150800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 143300,
"cur_price": 149900,
"tranches": 2,
"tranche_value": 2796943.490474186,
"last_add_price": 133800
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 147,729 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 1,
"tranche_value": 1467735.2954887256,
"last_add_price": 146300
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 42,
"entry_price": 114500,
"qty": 83,
"entry_price": 115981.92771084337,
"entry_at": "2026-08-24T13:12:24.667052+09:00",
"stop": 111200,
"target": 124400,
"peak": 115500,
"target": 130328,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,169 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4876724.210124999,
"last_add_price": 114500
"last_add_price": 117500
}
},
"realized_pnl": -24387110.163000036,
@@ -221,5 +221,5 @@
"003680": "2026-08-24"
},
"created_at": "2026-06-16T09:00:03.113416+09:00",
"updated_at": "2026-08-24T15:28:25.481722+09:00"
"updated_at": "2026-08-25T15:28:26.231406+09:00"
}
@@ -186,3 +186,4 @@
{"ts": "2026-08-20T09:30:25.177105+09:00", "side": "BUY", "code": "161890", "name": "한국콜마", "price": 133800, "qty": 20, "cost": 2676401, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 110654, "target": 183638, "signals": [{"label": "손절선", "ok": true, "detail": "110,654 (현재 133,900)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 183,638"}, {"label": "추세(60일선)", "ok": true, "detail": "92,498"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +529 · 기 -188"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 133,404"}]}
{"ts": "2026-08-24T13:12:24.666923+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 544, "proceeds": 2730984, "entry_price": 6333, "pnl": -714713, "pnl_pct": -20.58, "hold_from": "2026-07-30T09:04:08.798504+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 10,215"}, {"label": "추세(60일선)", "ok": true, "detail": "4,848"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:24.667054+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 114500, "qty": 42, "cost": 4809721, "path": "pullback", "reason": "눌림목 지정가 115,169 도달", "stop": 111200, "target": 124400, "signals": [{"label": "추세(60일선 위)", "ok": true, "detail": "114,200 vs 109,922"}, {"label": "정배열(5>60)", "ok": true, "detail": "116,300 / 109,922"}, {"label": "추세 기울기(60일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,300 / 109,922"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+13.5%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +48 · 기 -50 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+34%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.12% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "197,995 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.12% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "52"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 115,169 (현재 114,200)"}]}
{"ts": "2026-08-25T09:26:24.897394+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 117500, "qty": 41, "cost": 4818223, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 124400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 117,300)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 124,400"}, {"label": "추세(60일선)", "ok": true, "detail": "109,973"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 117,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 895265.3689999636,
"cash": 62840.52399996354,
"initial": 100000000,
"positions": {
"055550": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 104000,
"cur_price": 106000,
"tranches": 2,
"tranche_value": 5402457.352781249,
"last_add_price": 104900
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 4939827.041843749,
"last_add_price": 130800
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 4843602.718718749,
"last_add_price": 338000
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 128800,
"cur_price": 130800,
"tranches": 2,
"tranche_value": 4504266.321593748,
"last_add_price": 125200
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 115000,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4268049.827468748,
"last_add_price": 122900
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4370285.517874998,
"last_add_price": 195400
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 3257573.4203829807,
"last_add_price": 14290
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 38,
"entry_price": 114400,
"qty": 45,
"entry_price": 115100.0,
"entry_at": "2026-08-24T13:12:24.704448+09:00",
"stop": 99078,
"target": 160366,
"peak": 115100,
"stop": 100149,
"target": 159952,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 114000,
"tranches": 1,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 4349667.340562498,
"last_add_price": 114400
"last_add_price": 118900
}
},
"realized_pnl": -33040066.417000026,
@@ -202,5 +202,5 @@
"090850": "2026-08-06"
},
"created_at": "2026-06-16T09:00:03.113433+09:00",
"updated_at": "2026-08-24T15:28:25.484165+09:00"
"updated_at": "2026-08-25T15:28:26.233742+09:00"
}
@@ -101,3 +101,4 @@
{"ts": "2026-08-07T09:02:15.667162+09:00", "side": "BUY", "code": "252990", "name": "샘씨엔에스", "price": 14290, "qty": 208, "cost": 2972766, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 10340, "target": 21740, "signals": [{"label": "손절선", "ok": true, "detail": "10,340 (현재 14,240)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 21,740"}, {"label": "추세(10일선)", "ok": true, "detail": "12,997"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -216 · 기 +313"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 13,936"}]}
{"ts": "2026-08-24T13:12:24.669403+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 1042, "proceeds": 5231040, "entry_price": 6333, "pnl": -1369090, "pnl_pct": -20.58, "hold_from": "2026-07-30T09:04:08.809546+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 10,215"}, {"label": "추세(10일선)", "ok": false, "detail": "5,148"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:24.704450+09:00", "side": "BUY", "code": "078930", "name": "GS", "price": 114400, "qty": 38, "cost": 4347852, "path": "immediate", "reason": "조건 충족 — 즉시매수(눌림 안 기다림)", "stop": 99078, "target": 160366, "signals": [{"label": "추세(10일선 위)", "ok": true, "detail": "114,400 vs 83,520"}, {"label": "정배열(5>10)", "ok": true, "detail": "88,240 / 83,520"}, {"label": "추세 기울기(10일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "88,240 / 75,495"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+69.2%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -224 · 기 +284 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+22%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.55% (환산) vs 평균 0.42% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": true, "detail": "고점 87,600"}, {"label": "거래량 급증", "ok": false, "detail": "508,837 (환산) vs 평균 392,182"}, {"label": "회전율 급증", "ok": false, "detail": "0.55% (환산) vs 평균 0.42%"}, {"label": "RSI", "ok": true, "detail": "81"}, {"label": "눌림목 도달", "ok": false, "detail": "지정가 83,520 (현재 114,400)"}]}
{"ts": "2026-08-25T11:06:23.981941+09:00", "side": "BUY", "code": "078930", "name": "GS", "price": 118900, "qty": 7, "cost": 832425, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 99078, "target": 160366, "signals": [{"label": "손절선", "ok": true, "detail": "99,078 (현재 118,400)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 160,366"}, {"label": "추세(10일선)", "ok": true, "detail": "83,920"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +10 · 기 +116"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,138"}]}
@@ -1,5 +1,5 @@
{
"cash": 780251.9234999605,
"cash": 75146.17349996034,
"initial": 100000000,
"positions": {
"055550": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 104000,
"cur_price": 106000,
"tranches": 2,
"tranche_value": 5666061.52171875,
"last_add_price": 99700
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 4789675.2297187485,
"last_add_price": 130800
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 4623660.577343748,
"last_add_price": 338000
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 315500,
"cur_price": 314000,
"tranches": 2,
"tranche_value": 4236521.005156248,
"last_add_price": 295000
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 4249087.450874997,
"last_add_price": 195400
@@ -118,12 +118,12 @@
"entry_at": "2026-08-06T09:06:13.282466+09:00",
"stop": 87523,
"target": 137554,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "immediate",
"entry_reason": "조건 충족 — 즉시매수(눌림 안 기다림)",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 4279461.011749998,
"last_add_price": 101700
@@ -139,12 +139,12 @@
"entry_at": "2026-08-19T09:18:18.228991+09:00",
"stop": 112627,
"target": 185097,
"peak": 143600,
"peak": 150800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 143300,
"cur_price": 149900,
"tranches": 2,
"tranche_value": 4367140.436124997,
"last_add_price": 135000
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 38,
"entry_price": 114500,
"qty": 44,
"entry_price": 114909.09090909091,
"entry_at": "2026-08-24T13:12:24.707358+09:00",
"stop": 111200,
"target": 124400,
"peak": 115500,
"target": 126036,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 117,879 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 4404531.535843748,
"last_add_price": 114500
"last_add_price": 117500
}
},
"realized_pnl": -35361770.81650004,
@@ -210,5 +210,5 @@
"003680": "2026-08-24"
},
"created_at": "2026-06-16T09:00:03.113489+09:00",
"updated_at": "2026-08-24T15:28:25.486487+09:00"
"updated_at": "2026-08-25T15:28:26.236088+09:00"
}
@@ -118,3 +118,4 @@
{"ts": "2026-08-20T09:34:23.528819+09:00", "side": "BUY", "code": "161890", "name": "한국콜마", "price": 135000, "qty": 5, "cost": 675101, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 112154, "target": 183938, "signals": [{"label": "손절선", "ok": true, "detail": "112,154 (현재 134,900)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 183,938"}, {"label": "추세(20일선)", "ok": true, "detail": "100,310"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +529 · 기 -188"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 134,629"}]}
{"ts": "2026-08-24T13:12:24.707215+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 1007, "proceeds": 5055333, "entry_price": 6333, "pnl": -1323074, "pnl_pct": -20.58, "hold_from": "2026-07-30T09:04:08.817092+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 10,215"}, {"label": "추세(20일선)", "ok": true, "detail": "4,754"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:12:24.707360+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 114500, "qty": 38, "cost": 4351653, "path": "pullback", "reason": "눌림목 지정가 117,879 도달", "stop": 111200, "target": 124400, "signals": [{"label": "추세(20일선 위)", "ok": true, "detail": "114,200 vs 113,265"}, {"label": "정배열(5>20)", "ok": true, "detail": "116,300 / 113,265"}, {"label": "추세 기울기(20일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "116,300 / 109,922"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+13.5%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 +48 · 기 -50 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+34%"}, {"label": "목표가 리비전", "ok": true, "detail": "상향 ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "0.12% (환산) vs 평균 0.17% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 123,300"}, {"label": "거래량 급증", "ok": false, "detail": "197,995 (환산) vs 평균 289,522"}, {"label": "회전율 급증", "ok": false, "detail": "0.12% (환산) vs 평균 0.17%"}, {"label": "RSI", "ok": true, "detail": "52"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 117,879 (현재 114,200)"}]}
{"ts": "2026-08-25T09:26:25.500637+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 117500, "qty": 6, "cost": 705106, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 124400, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 117,300)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 124,400"}, {"label": "추세(20일선)", "ok": true, "detail": "113,420"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 117,189"}]}
@@ -1,5 +1,5 @@
{
"cash": 9035146.744000008,
"cash": 1543623.1840000078,
"initial": 100000000,
"positions": {
"095610": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 144,129 도달",
"cur_price": 133600,
"cur_price": 139000,
"tranches": 2,
"tranche_value": 2602335.520457338,
"last_add_price": 147600
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3682720.3330000006,
"last_add_price": 13420
@@ -53,14 +53,14 @@
"qty": 79,
"entry_price": 91727.84810126582,
"entry_at": "2026-08-04T09:34:11.251794+09:00",
"stop": 109994,
"stop": 111762,
"target": 117299,
"peak": 121000,
"peak": 123500,
"trailing_on": true,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3663769.807718751,
"last_add_price": 93500
@@ -81,7 +81,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 3726510.2558437507,
"last_add_price": 198600
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 3852866.4074375005,
"last_add_price": 417500
@@ -113,20 +113,20 @@
"sources": [
"auto"
],
"qty": 33,
"entry_price": 115400,
"qty": 65,
"entry_price": 116778.46153846153,
"entry_at": "2026-08-14T11:18:18.986875+09:00",
"stop": 111200,
"target": 128000,
"peak": 117900,
"target": 133514,
"peak": 120500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 115,362 도달",
"cur_price": 115000,
"tranches": 1,
"cur_price": 120300,
"tranches": 2,
"tranche_value": 3859995.6555625005,
"last_add_price": 115400
"last_add_price": 118200
},
"001450": {
"code": "001450",
@@ -139,12 +139,12 @@
"entry_at": "2026-08-20T13:20:26.879988+09:00",
"stop": 45584,
"target": 61809,
"peak": 50700,
"peak": 50900,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 50600,
"cur_price": 50400,
"tranches": 2,
"tranche_value": 3788581.3199375006,
"last_add_price": 50400
@@ -155,20 +155,20 @@
"sources": [
"auto"
],
"qty": 75,
"entry_price": 49500,
"qty": 147,
"entry_price": 50479.5918367347,
"entry_at": "2026-08-24T14:50:23.230803+09:00",
"stop": 44142,
"target": 65573,
"peak": 49650,
"stop": 45047,
"target": 66777,
"peak": 52000,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 49450,
"tranches": 1,
"cur_price": 51800,
"tranches": 2,
"tranche_value": 3745061.4761875006,
"last_add_price": 49500
"last_add_price": 51500
}
},
"realized_pnl": -41528108.856500044,
@@ -207,5 +207,5 @@
"316140": "2026-08-20"
},
"created_at": "2026-06-16T10:45:02.136776+09:00",
"updated_at": "2026-08-24T15:28:25.494295+09:00"
"updated_at": "2026-08-25T15:28:26.243940+09:00"
}
@@ -192,3 +192,5 @@
{"ts": "2026-08-21T12:24:25.556975+09:00", "side": "BUY", "code": "001450", "name": "현대해상", "price": 50400, "qty": 75, "cost": 3780567, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 44865, "target": 61004, "signals": [{"label": "손절선", "ok": true, "detail": "44,865 (현재 50,400)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 61,004"}, {"label": "추세(40일선)", "ok": true, "detail": "37,928"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +158 · 기 +906"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 50,252"}]}
{"ts": "2026-08-24T14:50:22.793047+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5010, "qty": 1190, "proceeds": 5950274, "entry_price": 6017, "pnl": -1210650, "pnl_pct": -16.73, "hold_from": "2026-07-30T09:00:24.268702+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,014 (현재 5,010)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 9,024"}, {"label": "추세(40일선)", "ok": true, "detail": "4,716"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T14:50:23.230821+09:00", "side": "BUY", "code": "257720", "name": "실리콘투", "price": 49500, "qty": 75, "cost": 3713057, "path": "breakout", "reason": "거래량·회전율 동반 전고점 돌파", "stop": 44142, "target": 65573, "signals": [{"label": "추세(40일선 위)", "ok": true, "detail": "49,500 vs 36,645"}, {"label": "정배열(5>40)", "ok": true, "detail": "43,690 / 36,645"}, {"label": "추세 기울기(40일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "43,690 / 36,266"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+35.2%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -652 · 기 +1,183 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+17%"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "4.25% (환산) vs 평균 1.56% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": true, "detail": "고점 48,250"}, {"label": "거래량 급증", "ok": true, "detail": "2,788,392 (환산) vs 평균 1,023,767"}, {"label": "회전율 급증", "ok": true, "detail": "4.25% (환산) vs 평균 1.56%"}, {"label": "RSI", "ok": true, "detail": "69"}]}
{"ts": "2026-08-25T09:34:22.177260+09:00", "side": "BUY", "code": "138040", "name": "메리츠금융지주", "price": 118200, "qty": 32, "cost": 3782967, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 111200, "target": 128000, "signals": [{"label": "손절선", "ok": true, "detail": "111,200 (현재 118,200)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 128,000"}, {"label": "추세(40일선)", "ok": true, "detail": "110,258"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -39 · 기 +19"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 118,089"}]}
{"ts": "2026-08-25T10:02:23.615869+09:00", "side": "BUY", "code": "257720", "name": "실리콘투", "price": 51500, "qty": 72, "cost": 3708556, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 44142, "target": 65573, "signals": [{"label": "손절선", "ok": true, "detail": "44,142 (현재 51,400)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 65,573"}, {"label": "추세(40일선)", "ok": true, "detail": "36,692"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -427 · 기 +1,106"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 51,311"}]}
@@ -1,5 +1,5 @@
{
"cash": 202056.64449999435,
"cash": 23779.90699999433,
"initial": 100000000,
"positions": {
"010950": {
@@ -18,7 +18,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 141100,
"cur_price": 140100,
"tranches": 2,
"tranche_value": 4497110.087925,
"last_add_price": 130800
@@ -39,7 +39,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 416000,
"cur_price": 414000,
"tranches": 2,
"tranche_value": 4537990.22125,
"last_add_price": 353000
@@ -60,7 +60,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 12900,
"cur_price": 13400,
"tranches": 2,
"tranche_value": 3916421.3406499997,
"last_add_price": 13420
@@ -76,12 +76,12 @@
"entry_at": "2026-08-04T09:34:11.254802+09:00",
"stop": 80343,
"target": 125803,
"peak": 121000,
"peak": 123500,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 114000,
"cur_price": 121700,
"tranches": 2,
"tranche_value": 3911396.213525,
"last_add_price": 93500
@@ -102,7 +102,7 @@
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 13,170 도달",
"cur_price": 13310,
"cur_price": 12780,
"tranches": 2,
"tranche_value": 3790684.47274563,
"last_add_price": 13830
@@ -123,7 +123,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 194100,
"cur_price": 189500,
"tranches": 2,
"tranche_value": 3913569.3666999997,
"last_add_price": 198600
@@ -144,7 +144,7 @@
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 113300,
"cur_price": 111500,
"tranches": 2,
"tranche_value": 4106448.2651999993,
"last_add_price": 117800
@@ -160,12 +160,12 @@
"entry_at": "2026-08-19T09:18:18.258948+09:00",
"stop": 114391,
"target": 186860,
"peak": 143600,
"peak": 150800,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 143300,
"cur_price": 149900,
"tranches": 2,
"tranche_value": 3939438.35845,
"last_add_price": 135000
@@ -181,12 +181,12 @@
"entry_at": "2026-08-21T09:48:21.674856+09:00",
"stop": 44391,
"target": 66195,
"peak": 50700,
"peak": 50900,
"trailing_on": false,
"scaled_out": false,
"entry_path": "breakout",
"entry_reason": "거래량·회전율 동반 전고점 돌파",
"cur_price": 50600,
"cur_price": 50400,
"tranches": 2,
"tranche_value": 3906404.1509499997,
"last_add_price": 50500
@@ -197,20 +197,20 @@
"sources": [
"interest"
],
"qty": 75,
"entry_price": 32950,
"qty": 80,
"entry_price": 33118.75,
"entry_at": "2026-08-24T13:14:24.966125+09:00",
"stop": 22546,
"target": 64161,
"peak": 34700,
"stop": 22658,
"target": 64501,
"peak": 35650,
"trailing_on": false,
"scaled_out": false,
"entry_path": "pullback",
"entry_reason": "눌림목 지정가 33,047 도달",
"cur_price": 34500,
"tranches": 1,
"cur_price": 34550,
"tranches": 2,
"tranche_value": 2490747.3361213473,
"last_add_price": 32950
"last_add_price": 35650
}
},
"realized_pnl": -25404766.05250001,
@@ -237,5 +237,5 @@
"316140": "2026-08-21"
},
"created_at": "2026-06-16T14:30:00.114884+09:00",
"updated_at": "2026-08-24T15:28:25.496319+09:00"
"updated_at": "2026-08-25T15:28:26.246027+09:00"
}
@@ -91,3 +91,4 @@
{"ts": "2026-08-24T13:12:24.718534+09:00", "side": "SELL", "code": "003680", "name": "한성기업", "price": 5030, "qty": 1152, "proceeds": 5783261, "entry_price": 6333, "pnl": -1513534, "pnl_pct": -20.58, "hold_from": "2026-07-30T09:04:08.840088+09:00", "reason": "손절", "partial": false, "signals": [{"label": "손절선", "ok": false, "detail": "5,039 (현재 5,030)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 10,215"}, {"label": "추세(10일선)", "ok": false, "detail": "5,148"}, {"label": "수급(외/기)", "ok": false, "detail": "외 -31 · 기 +0"}, {"label": "분할(2/2)", "ok": null, "detail": "추가 종료"}]}
{"ts": "2026-08-24T13:14:24.966126+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 32950, "qty": 75, "cost": 2471621, "path": "pullback", "reason": "눌림목 지정가 33,047 도달", "stop": 22546, "target": 64161, "signals": [{"label": "추세(10일선 위)", "ok": true, "detail": "33,000 vs 28,622"}, {"label": "정배열(5>10)", "ok": true, "detail": "35,100 / 28,622"}, {"label": "추세 기울기(10일선↑)", "ok": true, "detail": "우상향"}, {"label": "중기 정배열(5>60)", "ok": true, "detail": "35,100 / 31,286"}, {"label": "상대강도(시장대비)", "ok": true, "detail": "+24.6%p"}, {"label": "수급(5일 외/기)", "ok": true, "detail": "외 -62 · 기 +65 (천주)"}, {"label": "상승여력", "ok": true, "detail": "+106%"}, {"label": "어닝 서프라이즈", "ok": true, "detail": "+ ➕"}, {"label": "시장 강세", "ok": true, "detail": "강세"}, {"label": "과열 아님(회전율)", "ok": true, "detail": "3.32% (환산) vs 평균 3.74% (보류 3배↑)"}, {"label": "돌파(전고점)", "ok": false, "detail": "고점 45,300"}, {"label": "거래량 급증", "ok": false, "detail": "298,285 (환산) vs 평균 409,191"}, {"label": "회전율 급증", "ok": false, "detail": "3.32% (환산) vs 평균 3.74%"}, {"label": "RSI", "ok": true, "detail": "56"}, {"label": "눌림목 도달", "ok": true, "detail": "지정가 33,047 (현재 33,000)"}]}
{"ts": "2026-08-24T15:04:21.165717+09:00", "side": "BUY", "code": "001450", "name": "현대해상", "price": 50500, "qty": 77, "cost": 3889083, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 43920, "target": 65039, "signals": [{"label": "손절선", "ok": true, "detail": "43,920 (현재 50,600)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 65,039"}, {"label": "추세(10일선)", "ok": true, "detail": "40,160"}, {"label": "수급(외/기)", "ok": true, "detail": "외 +168 · 기 +582"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 50,563"}]}
{"ts": "2026-08-25T13:44:26.700023+09:00", "side": "BUY", "code": "327260", "name": "RF머트리얼즈", "price": 35650, "qty": 5, "cost": 178277, "path": "add", "reason": "추격매수 — 2/2차 (추세 지속)", "stop": 22546, "target": 64161, "signals": [{"label": "손절선", "ok": true, "detail": "22,546 (현재 35,650)"}, {"label": "목표/트레일링", "ok": null, "detail": "목표 64,161"}, {"label": "추세(10일선)", "ok": true, "detail": "28,887"}, {"label": "수급(외/기)", "ok": true, "detail": "외 -118 · 기 +90"}, {"label": "분할(1/2)", "ok": null, "detail": "추가 트리거 35,565"}]}

Some files were not shown because too many files have changed in this diff Show More