콘텐츠로 이동

실행 모델

QuantiqDSL은 이벤트가 들어올 때마다 스크립트 전체를 다시 실행합니다.

제품 경계 요약

Quantiq의 자동매매 실행 모델은 cloud + runtime 경계를 전제로 합니다.

  • cloud는 사용자 계정, 스크립트/커뮤니티 메타데이터, 라이선스와 운영 메타데이터를 관리합니다.
  • 실제 자동매매 실행과 주문 전송은 사용자의 PC에서 동작하는 로컬 runtime 이 담당합니다.
  • 브로커 자격정보와 민감한 실행 상태는 로컬 runtime 경계에서 처리하며, landing에서 안내하는 내 PC에서 직접 실행이라는 문구도 이 모델을 뜻합니다.
  • 원격 모니터링/제어는 cloud relay를 거칠 수 있지만, 브라우저가 브로커를 직접 호출하는 구조는 아닙니다.

따라서 실행 모델을 이해할 때는 “cloud에서 전략을 관리하고, runtime에서 실제 주문을 실행한다”는 분리를 기본으로 보면 됩니다.

로컬 실행, 플랫폼, 개인정보 경계

현재 landing에서 노출하는 로컬 실행 관련 문구는 아래 범위를 뜻합니다.

  • 로컬 runtime은 현재 Win / Mac 지원을 기준으로 사용자에게 안내합니다.
  • 실제 자동매매 주문 전송은 cloud relay가 아니라 사용자의 PC에서 실행 중인 runtime이 담당합니다.
  • 브로커 자격정보와 실행 민감정보는 로컬 runtime 경계에서 처리하는 것을 기본 전제로 합니다.
  • 다만 cloud는 사용자 계정, 인증, 라이선스, 스크립트/커뮤니티 메타데이터 같은 cloud 책임 데이터를 계속 보관합니다.

즉 landing의 개인정보 서버 미보관 또는 로컬 보관 문구는 “모든 사용자 정보가 서버에 전혀 없다”는 뜻이 아니라, 브로커 실행 자격정보와 주문 실행 민감정보를 cloud가 직접 보관/중계하지 않는다는 뜻으로 이해해야 합니다.

landing의 클라우드 상태 비종속도 같은 맥락에서 해석합니다.

  • 주문 전송과 runtime 내부 운영 루프는 cloud를 경유하지 않습니다.
  • 하지만 로그인, 라이선스 확인, 스크립트/커뮤니티, 원격 알림/제어 같은 기능은 cloud 의존성이 남아 있을 수 있습니다.
  • 특히 cloud relay 인증이 치명적으로 실패하면 runtime은 이를 운영 실패로 취급해 엔진을 멈추고, grace 이후에는 재로그인 전까지 API/WS 접근과 엔진 재시작을 막을 수 있습니다.
  • 따라서 이 문구는 “자동매매 실행 루프가 cloud proxy에 종속되지 않는다”는 의미이지, 제품 전체가 cloud 없이 완전히 동작한다는 절대 보장은 아닙니다.

이벤트 타입

price_change

  • 실시간 가격 변동 이벤트
  • 빈도가 높음
  • event == "price_change"

candle_close

  • 봉 마감 이벤트
  • 확정 OHLCV 기준 판단에 적합
  • event == "candle_close"
if event == "candle_close":
    # 지표 기반 판단은 보통 여기서 수행
    pass

실행 순서

  1. 파서가 메타데이터/안전성 검증
  2. 샌드박스에서 코드 실행
  3. buy/sell/hold 마지막 호출을 최종 결정으로 채택
  4. 차트/로그/var 상태를 수집

의사결정 규칙

한 번의 실행에서 buy/sell/hold를 여러 번 호출하면 마지막 호출만 유효합니다.

buy(tag="first")
sell(tag="second")
# 최종 결정: SELL

상태 유지 방식

일반 지역 변수는 이벤트마다 초기화됩니다. 상태를 유지하려면 var를 사용하세요.

var.init(count=0)
var.count = var.count + 1
log("run count:", var.count)

조건검색 전략 동작

거래 탭의 전략 편집 패널에서 종목검색식(조건검색) 을 연결한 전략은 고정 종목 바인딩과 별도로 동작합니다.

  • 엔진 시작 직후 조건 결과를 한 번 조회합니다.
  • 전략 저장, 활성화/비활성화, 삭제 직후에도 조건 결과를 다시 조회합니다.
  • enabled 전략이면 엔진 상태와 무관하게 background loop가 주기적으로 조건 결과를 갱신합니다.
  • 조건식 선택자는 내부적으로 seq 기준으로 저장됩니다. 이전 입력 형식인 id 또는 condition_seq를 사용해도 저장 시 canonical seq 형태로 정리됩니다.

조건 결과에서 matched 된 종목은 runtime-managed symbol universe에 들어가며, 별도의 고정 종목 바인딩이 없어도 다음 경로에 연결됩니다.

  • 실시간 candle 업데이트
  • 거래 탭 차트 갱신
  • 해당 전략 컨텍스트에서의 live DSL 평가
  • 거래 탭 전략/종목 표면 집계

조건 결과가 들어오면 runtime이 빈 슬롯을 runtime-managed symbol binding으로 채울 수 있습니다. 이 종목은 사용자가 수동으로 종목 목록에 추가하지 않았더라도 전략의 tracked symbol처럼 보일 수 있습니다.

이미 보유 중인 포지션이 있거나 미체결/재조정 대상 주문이 남아 있는 조건 종목은 다음 refresh에서 바로 제거되지 않을 수 있습니다. 반대로 idle 상태가 되면 이후 refresh에서 runtime-managed binding이 정리될 수 있습니다.

따라서 조건검색 전략은 “저장만 해두고 엔진 재시작 전까지 대기”하는 방식이 아니라, enabled 상태라면 저장 직후에도 결과가 반영될 수 있습니다.

landing에서 말하는 발굴은 넓게, 판단은 정교하게는 이 조건검색 + 스크립트 연결 모델을 요약한 표현입니다.

  • 조건검색은 후보군을 넓게 모으는 역할을 합니다.
  • 실제 진입/청산/리스크 판단은 전략 스크립트가 담당합니다.
  • 즉 검색식은 종목 유니버스를 공급하고, 스크립트는 그 종목에 대해 DSL 의사결정을 수행합니다.

landing과 시작 가이드에서 설명하는 실제 주문 도달 흐름은 아래 순서를 기준으로 읽으면 됩니다.

  1. 스크리닝: 종목 지정 또는 HTS 조건검색으로 감시 후보를 모읍니다.
  2. 위험종목 필터링: 관리종목, 거래정지, 급격한 이상 종목 같은 기본 제외 대상을 먼저 걸러냅니다.
  3. 스크립트 의사결정: 전략 스크립트가 후보 종목별 진입/청산 조건을 평가합니다.
  4. 리스크 검사: 계좌, 전략, 포지션 한도와 런타임 리스크 규칙을 다시 확인합니다.
  5. 주문 실행: 위 단계를 모두 통과한 주문만 로컬 runtime이 브로커 submit 단계로 넘깁니다.

즉, 검색식이 바로 주문을 내는 구조가 아니라 후보 수집 → 필터링 → 스크립트 판단 → 리스크 검사 → 주문 실행의 순서로 이어지는 모델입니다.

startup bootstrap

runtime 서버가 시작되면 첫 화면 진입 전에 다음 bootstrap이 한 번 선행됩니다.

  • enabled 전략과 조건검색 결과 재계산
  • runtime-managed symbol binding 복원
  • 감시 종목의 기본정보/호가 snapshot warm-up
  • 거래 탭 차트용 historical candle과 runtime overlay seed

이 과정 덕분에 거래 탭은 첫 WebSocket delta를 기다리지 않아도 초기 종목 목록, 시세 요약, 차트 오버레이를 바로 표시할 수 있습니다.

브라우저의 앱 진입 gating은 위 bootstrap 전체와 1:1로 묶지 않습니다.

  • 브라우저는 /api/readyfalse인 동안 로그인/메인 화면 대신 런타임 기동 대기 중입니다. 완료되면 자동으로 이동합니다. 대기 화면만 보여줍니다.
  • /api/readyengine=RUNNING 여부가 아니라, 원장 복원과 필수 서비스 기동 같은 기본 운영 가능 상태와 DB 확인을 기준으로 판단합니다.
  • 차트 warm-up, background seed, chart preload 같은 후속 준비 작업은 /api/ready의 gating 대상이 아닙니다.
  • 따라서 사용자는 chart warm-up이 완전히 끝나기 전에도 앱에 진입할 수 있고, 이후 차트 준비는 background에서 이어질 수 있습니다.

주문이 차단되거나 대기 중일 때

주문은 대략 다음 단계를 거칩니다.

  1. DSL 결정
  2. symbol state / symbol policy gate
  3. risk gate
  4. 브로커 submit
  5. 체결통보 또는 reconcile

2~3단계에서 막히면 주문은 order_blocked 계열 이벤트와 차트 차단 마커로 남습니다.

반대로 주문이 생성된 뒤 PENDING, SUBMITTED, PARTIALLY_FILLED에 머무는 경우는 보통 브로커 submit 이후 단계의 문제입니다. 이때는 다음 필드를 함께 확인해야 합니다.

  • repair_required
  • repair_reason
  • submit_status
  • status_source
  • status_confidence
  • status_detail
  • broker_submit_error_code
  • broker_submit_error_message

legacy reconcile_required는 아직 같이 내려올 수 있지만, 현재 구현에서는 repair_required의 호환 alias로만 봐야 합니다.

이 값으로 “전략/리스크 차단”인지, “브로커 제출은 했지만 startup/reconnect/WS miss repair가 필요한지”를 구분할 수 있습니다. status_source/status_confidence/status_detail는 거래 탭이 이 판단을 프런트 ad-hoc 규칙이 아니라 서버 canonical contract로 설명할 때 사용하는 메타입니다.

브로커 submit 단계에서는 다음 동작도 함께 알아두는 것이 좋습니다.

  • KIS 시장가 주문은 현재가 숫자를 참고해 생성되더라도, 브로커 전송 payload에서는 시장가 규칙에 맞춰 ORD_UNPR=0으로 제출됩니다.
  • KIS submit/cancel POST는 요청 body 기준 hashkey를 먼저 생성해야 하며, 이 생성이 실패하면 runtime은 주문/취소 요청 자체를 보내지 않습니다.
  • KIS 정정/취소는 브로커 ODNO가 새로 발급될 수 있습니다. 거래 탭은 이때도 새 broker 주문번호를 별도 주문으로 끊어 보여주지 않고, 같은 logical order chain으로 계속 추적해야 합니다.

거래 탭의 주문 패널은 이 메타데이터를 그대로 보여주므로, WS 반영, repair 대기, reconcile 보정, 브로커 응답, 로컬 상태를 화면에서 바로 구분할 수 있어야 합니다.

현재 KIS order notice 처리에서는 다음 즉시 반영 규칙도 알아두면 좋습니다.

  • ORDER_ACCEPTED, AMEND_ACCEPTED, FILL, CANCEL_ACCEPTED notice는 가능한 한 reconcile보다 먼저 local order state에 반영됩니다.
  • 따라서 정상 경로에서는 거래 탭의 주문 상태와 체결 수량이 periodic reconcile을 기다리지 않고 먼저 갱신될 수 있습니다.
  • paper 기준 CANCEL_ACCEPTED는 high-confidence authoritative cancel signal로 취급되며, lineage를 갱신한 뒤 주문을 즉시 최종 CANCELLED로 닫습니다.

주기 reconcile 이후에도 브로커 미체결/체결 조회와 active 주문이 매칭되지 않으면 다음 값이 추가로 남을 수 있습니다.

  • submit_status="RECONCILE_UNRESOLVED"
  • reconcile_note
  • last_reconcile_at

이 경우 거래 탭은 단순 대기 주문이 아니라 상태 확인 필요와 마지막 reconcile 사유를 함께 보여줍니다.

실제 화면에서 자주 보게 되는 주문 진단 문구는 대략 다음 의미입니다.

  • 전송 불확실 · ... · 재조정 대기: submit 응답이 timeout/예외 등으로 확정되지 않아 reconcile 결과를 기다리는 상태
  • 브로커 거부 · ...: submit은 완료됐지만 브로커가 주문을 거절한 상태
  • 상태 확인 필요 · ...: periodic/manual reconcile을 여러 번 돌았지만 브로커 open/filled 조회와 내부 주문을 확정적으로 매칭하지 못한 상태
  • 취소 요청 접수: broker cancel REST 요청은 수락됐지만, 아직 CANCEL_ACCEPTED notice를 받지 않아 최종 잔량 취소/체결 여부를 대기하는 상태

잔고 재조정(verify_balance)은 체결통보나 체결조회가 비어 있어도 내부 수량과 브로커 수량 차이를 사용해 일부 주문을 복구할 수 있습니다.

  • broker_qty - internal_qty 델타가 정확히 하나의 active BUY 또는 SELL 주문 수량과 일치하면 그 주문을 체결로 복구합니다.
  • 반대로 같은 심볼에 후보 주문이 여러 개여서 어느 주문이 체결됐는지 확정할 수 없으면, 포지션 수량만 브로커 기준으로 보정하고 주문은 계속 열어 둡니다.

warning/critical runtime notification은 브라우저 알림 목록과 텔레그램 알림 채널에 같은 본문으로 전달됩니다. 따라서 주문 차단, 브로커 거부, 재조정 대기, 외부 포지션 감지 같은 운영 이슈는 화면과 텔레그램에서 같은 사유 문장으로 확인할 수 있습니다.

주문 차단/거부 계열 notification은 다음처럼 사용자에게 직접 보일 수 있습니다.

  • 주문 차단: symbol policy, 리스크 규칙, 엔진 pause 등으로 주문이 생성되지 않았을 때
  • 주문 차단: 위 사유 외에도 정규장 주문 가능 시간(09:00 <= t < 15:20)이 지난 뒤 새 주문을 막을 때 같은 형식으로 발생할 수 있습니다.
  • 주문 거부: 브로커 submit 응답이 reject일 때
  • 주문 전송 불확실: submit timeout/예외 등으로 재조정 대기 상태가 됐을 때
  • 외부 포지션 감지: 내부 주문과 매칭되지 않는 외부 BUY 체결이 들어왔을 때

텔레그램 채널은 기본적으로 원격 운영 보조 수단입니다.

  • 경고/운영 알림을 같은 문장으로 전달합니다.
  • 사용 가능한 환경에서는 원격 확인 또는 제어 액션이 함께 노출될 수 있습니다.
  • 텔레그램이 있다고 해서 로컬 runtime 없이 독립 실행되는 것은 아닙니다. 자동매매 실행 주체는 계속 로컬 runtime입니다.

기본 strategy.order_on 정책이 봉 마감 실행인 전략이라면, 봉 마감 전 intra-bar price change에서는 주문 차단/거부 알림이 반복적으로 나가면 안 됩니다. 현재 runtime은 order_on 미충족 이벤트를 risk gate 이전에 걸러, 5분봉 마감 전략이 틱마다 같은 차단 메시지를 반복 발송하지 않도록 정리되어 있습니다.

외부 포지션과 인수 대기

HTS 같은 외부 채널에서 발생한 체결은 Quantiq 내부 주문과 먼저 매칭합니다.

  • 내부 주문번호와 매칭되면 일반 체결처럼 주문/포지션이 갱신됩니다.
  • 전략에 바인딩된 심볼에서 사용자가 QUANTIQ 콘솔로 직접 낸 수동 주문은 별도 인수 대기 단계 없이 그 전략 bucket 수동 주문으로 바로 생성됩니다.
  • 내부 주문과 매칭되지 않는 외부 BUY 체결이나 잔고-only 보유는 전략 포지션으로 자동 편입하지 않습니다.
  • 대신 같은 계좌/심볼에 대해 내부 포지션이 없고, PENDING, SUBMITTED, PARTIALLY_FILLED, reconcile_required=true, submit uncertainty 같은 미종결/미확정 QUANTIQ 주문도 없으면 미귀속(__manual__) 관리 대상으로 자동 승격합니다.
  • REJECTED, FAILED, CANCELLED, FILLED처럼 이미 끝난 주문 상태만 남아 있는 경우는 미귀속 승격 차단 근거로 쓰지 않습니다.
  • 이미 Quantiq가 관리 중인 포지션에 대해 외부 SELL/BUY delta가 들어오면, 그 포지션은 origin=external 변경 이력과 함께 수량/평단이 조정될 수 있습니다.

즉, “전략이 보고 있는 종목”이라는 이유만으로 외부 수동 매수가 바로 전략 포지션으로 합쳐지지는 않습니다. 현재 구현은 삭제/이관/legacy orphan뿐 아니라 broker-only 보유도 조건을 만족하면 미귀속으로 승격해 운영 콘솔에서 수동 관리할 수 있게 합니다.

전략 비활성화/삭제와 보유 포지션

전략 선택 패널에서 보유 포지션이 남은 전략을 비활성화하거나 삭제하면 즉시 실패시키지 않고 확인 플로우를 거칩니다.

  • 비활성화:
  • 경고: 비활성화 상태에서는 매도 조건이 되어도 매도되지 않습니다.
  • 사용자가 확인하면 전략만 enabled=false로 바뀌고, 보유 포지션 심볼은 계속 추적됩니다.
  • 즉, 차트/포지션 표시는 남지만 자동 매도는 멈춥니다.

  • 삭제:

  • 경고: 전략을 해제하면 남아 있던 포지션과 대기 주문은 미귀속(__manual__)으로 이관될 수 있습니다.
  • 사용자가 확인하면 전략은 삭제되고, 남아 있던 보유 포지션과 대기 주문은 미귀속 bucket으로 이동합니다.
  • 이때 실현 손익 원장은 삭제 직전까지의 값이 그대로 남습니다.
  • runtime은 이관 손익, 이관 시점 총손익, 이관 종목 수를 activity/notification과 transfer_snapshots 상태로 남길 수 있습니다.

즉, 보유 포지션이 있는 전략의 삭제는 “포지션 강제 청산”이 아니라 “전략 bucket에서 미귀속 bucket으로 ownership 이관”에 가깝습니다.

전략 저장에서도 비슷한 일이 발생할 수 있습니다.

  • 전략 편집 중 일부 종목을 전략에서 떼어내는데 보유 포지션이나 대기 주문이 남아 있으면, 확인 후 그 종목도 미귀속으로 이관될 수 있습니다.
  • 이 경우도 이관 손익, 이관 시점 총손익, 이관 종목 수가 같은 방식으로 기록될 수 있습니다.

대시보드/거래 숫자 해석

거래 탭의 전략/종목 표면은 runtime workspace row와 live quote를 기준으로 그려지고, 대시보드의 계좌 표면은 브로커 계좌 스냅샷과 runtime 원장을 함께 요약합니다. 따라서 다음 현상은 정상일 수 있습니다.

  • 체결 전에도 현재가, 등락, 일중 변동이 먼저 보인다
  • 첫 WebSocket delta 전에도 bootstrap된 quote/historical candle 기준으로 패널과 차트가 채워진다
  • 대시보드의 브로커 계좌 표는 브로커 계좌 현황으로 표시되며, 펼쳐보기로 계좌 전체 보유 종목/수량/평균 단가/현재가/평가손익/수익률을 확인할 수 있다
  • 대시보드의 상단 보유 종목 수는 브로커 계좌 스냅샷이 있으면 계좌 전체 보유 수를 우선 사용하고, 스냅샷이 없을 때만 runtime workspace row 집계로 fallback 한다
  • 브로커 계좌 표의 수량/평단은 브로커 계좌 스냅샷 기준이고, 현재가/평가금액/평가손익/수익률은 runtime live price cache 기준으로 다시 mark-to-market 된다
  • 브라우저는 구독 중인 종목에 대해서만 WebSocket 가격 patch를 fast-path로 반영하고, broker-only 보유 종목을 포함한 최종 계좌 표 숫자는 /api/state.account_summary.account_positions를 기준으로 본다
  • 거래 탭의 전략 요약은 실현 손익만이 아니라 현재 보유 포지션 기준 미실현 손익도 함께 보여준다
  • 거래 탭 종목 행의 평균 단가, 평가 금액, 종목 손익, 수익률은 현재 보유 포지션 기준 필드이며, 보유 수량이 없으면 로 표시된다

반대로 주문은 남아 있는데 포지션이 비어 있으면, 단순 표시 지연이 아니라 submit/reconcile/execution 중 하나가 아직 정리되지 않은 상태로 봐야 합니다.

runtime 계산 필드 기준

대시보드와 거래 탭은 브라우저에서 손익/보유/대기 숫자를 다시 계산하지 않고 runtime이 내려주는 표준 계산 필드를 그대로 사용합니다.

  • 계좌 카드와 계좌 요약은 /api/state.account_summary를 기준으로 봅니다.
  • 전략별 요약은 /api/state.strategies[].runtime_summary를 기준으로 봅니다.
  • 종목 행과 워크스페이스 표는 /api/state.workspace_rows[]를 기준으로 봅니다.

운영 상태 동기화가 새 polling 경로로 전환되는 동안에는 /api/state 와 별도로 runtime view polling API를 함께 제공합니다.

별도로 GET /api/orders 는 recent window가 아니라 active account_no 범위의 전체 주문 이력을 반환합니다. 운영 탭의 펼친 주문 목록은 UX상 최근 주문 중심으로 보일 수 있지만, API 자체는 최근 일부만 잘라서 내려주지 않습니다.

  • GET /api/view/manifest
  • runtimeInstanceId, manifestVersion, generatedAt, block별 version, dirty를 반환합니다.
  • 브라우저는 이 응답으로 어떤 block이 새로워졌는지 확인합니다.
  • GET /api/view/blocks?names=engine,workspace_rows,...
  • 요청한 block만 반환합니다.
  • 각 block은 version, dirty, updatedAt, stateRevision, data envelope로 내려옵니다.
  • dirty block은 반환 직전에 read-through recompute 되어 최신 canonical payload로 정리됩니다.
  • 현재 지원 block은 engine, orders, positions, account_summary, equity_curve, strategy_summaries, workspace_rows, notifications 입니다.
  • migration 동안 /api/state 는 rollback/compatibility 경로로 병행 유지될 수 있습니다.
  • 현재 기본 cadence는 core runtime block용 manifest/block 500ms, auxiliary config/activity용 legacy /api/state 3000ms 입니다.
  • intraday equity_curve 는 runtime view block으로 이동했고, point가 이미 존재하는 동안에는 현재 5분 bucket의 마지막 값을 따라가기 위해 약 5초 cadence refresh policy로 dirty 되어 steady state에서도 곡선이 이어집니다. 즉 자산곡선은 더 이상 legacy /api/state equity polling 주기에 묶이지 않습니다.
  • equity_curve 는 초단위 raw point가 아니라 5분 bucket last-value 시계열입니다. 브라우저는 이를 today 자산 흐름 차트의 canonical source로 사용합니다.
  • runtime은 같은 거래일/계좌 범위의 equity_curve bucket을 ledger projection으로 복구하므로, 재기동 뒤에도 이미 기록한 today 곡선을 이어서 보여줄 수 있습니다.
  • equity_curve payload에는 time만 있고 value는 없는 whitespace point가 포함될 수 있습니다. 이는 API 누락이나 오류가 아니라, runtime 미기동/미추적 시간대를 line break로 표현하기 위한 canonical gap입니다.
  • today 자산 흐름 차트는 브라우저에서 08:40~16:20 KST 고정 X축으로 렌더링되며, 사용자는 이 범위를 스크롤/줌으로 바꾸지 않습니다.
  • Dashboard / Trading의 핵심 운영 숫자(손익, 투자금, 평가금액, 보유/대기 수량)는 client-side 재집계보다 account_summary, runtime_summary, workspace_rows canonical field를 우선 사용합니다.
  • 특히 대시보드 총 평가자산과 intraday equity_curve는 브라우저가 cash/position 값을 다시 합산해 덮어쓰기보다 broker/account summary가 내려준 authoritative equity를 우선 사용합니다.
  • 대시보드 계좌 브리핑은 미실현 포함 토글을 제공하고, OFF에서는 실현손익만, ON에서는 실현손익 + 현재 평가손익을 같은 투입금액 대비 기준으로 보여줍니다.
  • 브로커 계좌 현황은 활성 전략 유무와 무관하게 최신 계좌 캐시나 broker refresh 결과를 우선 사용하므로, 장중이 아니어도 미수신으로 오래 고정되지 않는 쪽을 목표로 합니다.
  • 브라우저 store 안에서도 recent orders와 full order history를 분리합니다. 거래로그/토스트 같은 recent surface만 recent window를 써도 되고, 대시보드/운영 스트립/count surface와 운영 탭 상세 주문 이력은 full order history를 기준으로 봅니다.
  • 운영 탭의 최근 주문 요약은 단순 N주 상태 문구로 축약하지 않고, 체결이 발생한 주문이면 filled quantity와 대표 체결가를 함께 보여주는 쪽을 우선합니다.
  • websocket reconnect 뒤 차트는 patch만 이어붙이지 않고 최신 candle snapshot으로 재정합될 수 있으며, 이때 브라우저는 중복 timestamp candle을 제거한 뒤 그립니다.
  • 이 migration 단계에서는 canonical summary가 아직 비어 있으면 일부 카드/패널이 과거처럼 브라우저에서 다시 합산하지 않고 0 또는 빈 상태로 남을 수 있습니다. 숫자가 달라 보여도 브라우저 계산식이 바뀐 것이 아니라 runtime authoritative field를 더 직접적으로 따르기 시작한 것으로 해석하면 됩니다.
  • runtime view polling 경로를 즉시 꺼야 하면 브라우저 devtools 콘솔에서 localStorage.setItem('quantiq_runtime_view_polling_disabled', 'true') 를 실행한 뒤 새로고침하면 legacy /api/state polling 경로로 즉시 복귀합니다.
  • rollback 해제는 localStorage.removeItem('quantiq_runtime_view_polling_disabled') 후 새로고침입니다.

핵심 숫자 의미는 다음처럼 고정되어 있습니다.

  • realized_pnl: 누적 실현 손익
  • today_realized_pnl: 당일 실현 손익
  • unrealized_pnl: 현재 보유 수량 기준 평가 손익
  • total_pnl: realized_pnl + unrealized_pnl
  • invested_amount: 현재 보유 수량 기준 투입 금액
  • market_value: 현재가 x 보유 수량 기준 평가 금액
  • holding_count / holding: 현재 보유 여부 또는 보유 종목 수
  • pending_order_count / pending_count: 아직 정리되지 않은 활성 주문 수

따라서 같은 전략이라도 화면 위치에 따라 다른 값을 보여주는 것이 아니라, 각 화면이 같은 runtime 계산 필드를 다른 단위로 풀어 보여준다고 이해하면 됩니다.

운영 탭과 미귀속 종목

운영 탭(/operations)은 전략 테이블과 종목 테이블을 분리해 보여주는 실시간 운영 화면입니다.

  • 상단 전략 테이블은 실제 전략만 보여줍니다.
  • 하단 종목 테이블은 전략 종목과 함께, 전략 바인딩 없이 남아 있는 고아 포지션/고아 대기 주문 종목도 보여줄 수 있습니다.
  • 이런 종목은 전략명 대신 미귀속으로 표시됩니다.
  • 미귀속은 전략 자동매매 대상이 아니라 콘솔 수동 관리 대상입니다.
  • 전체 보기에서는 미귀속 종목이 보입니다.
  • 전략별 보기에서는 선택한 실제 전략 종목만 보여주므로 미귀속 종목은 섞이지 않습니다.
  • 운영 탭 상태 라벨은 내부 import 플래그 대신 보유, 주문대기, 차단, 감시, 비활성 같은 직접 상태를 우선 보여줍니다.

운영 탭의 종목 행은 펼침/접힘으로 상세를 볼 수 있습니다.

  • 펼치면 차트, 최근 주문, 정책 상태, 누적 실현손익, 총손익 같은 상세를 같은 행 안에서 확인합니다.
  • 전략 바인딩이 없는 미귀속 종목은 전략 DSL overlay를 전제로 하지 않고, 기본 candle 차트 fallback으로 봅니다.
  • orphan PENDING / SUBMITTED 주문도 synthetic workspace_row로 backfill 되므로 /api/state, /api/workspace, runtime view block, remote snapshot 어디서 보더라도 같은 종목 행이 나타나야 합니다.

운영 탭의 전역 보조 UI는 다음처럼 나뉩니다.

  • 하단 운영 스트립: 엔진 상태, 미실현손익, 오늘 실현손익, 대기주문 같은 세션 요약을 보여줍니다.
  • 거래로그 패널: INFO 이상 이벤트만 시간순으로 보여줍니다. 지우기닫기 버튼으로 제어합니다.
  • 우측 하단 토스트: 주문 발행, 체결, 거부, 연결 오류 같은 즉시성 이벤트를 짧게 보여줍니다.

수동 주문과 대기 주문 취소는 운영 탭에서 직접 수행할 수 있지만, 사용자에게는 raw 500 대신 원인이 해석된 한글 메시지로 안내해야 합니다. 예를 들어 브로커 미연결, 전송 실패, 거부, 전송 불확실은 서로 다른 메시지로 보여야 합니다.

미귀속 종목 수동 주문은 strategy_id를 항상 사용자가 직접 고르는 방식이 아닙니다.

  • retained 포지션 owner가 하나로 확정되면 그 원장을 기준으로 주문을 계속 진행할 수 있습니다.
  • active orphan 주문 owner가 하나로 확정되면 그 원장을 기준으로 주문을 계속 진행할 수 있습니다.
  • owner가 여러 개이거나 아예 없으면 주문이 막히고, 원인을 설명하는 한글 메시지가 보여야 합니다.

대기 주문 취소도 운영 탭에서 직접 수행할 수 있습니다.

  • 종목 행 작업 열의 취소는 빠른 액션입니다.
  • 펼친 주문 목록에서도 개별 주문을 직접 취소할 수 있습니다.
  • 운영 탭은 브라우저가 status만 보고 추정하지 않고 runtime order payload의 cancel_allowed, cancel_reason를 기준으로 취소 버튼과 안내 문구를 결정합니다.
  • 취소 가능한 상태는 active pending 계열 주문이고, 이미 FILLED, REJECTED, FAILED 된 주문에는 취소 액션을 노출하지 않아야 합니다.
  • PENDING 주문은 broker submit 전이면 로컬에서 즉시 취소될 수 있습니다.
  • UNKNOWN, RECONCILE_UNRESOLVED 같은 미확정 주문도 가능한 한 취소 시도 대상입니다. broker order id가 없더라도 open inquiry에서 대상을 찾으면 그 주문번호로 취소를 시도합니다.
  • 조회 기반 취소에서도 대상을 찾지 못하면 runtime은 false final cancel을 만들지 않고, 사용자에게 주문 접수 또는 체결 여부를 다시 확인하라는 메시지를 남깁니다.
  • 취소 요청 REST accepted만으로는 최종 CANCELLED와 다릅니다. 내부 주문은 먼저 cancel_requested 중간 상태로 남고, 정상 success path에서는 CANCEL_ACCEPTED notice가 오면 즉시 CANCELLED로 닫습니다.
  • 다만 취소 이후라도 late FILL evidence가 더 강하게 도착하면, runtime은 false cancel을 피하기 위해 최종 상태를 PARTIALLY_FILLED 또는 FILLED로 다시 교정합니다.
  • 취소 요청도 브로커 hashkey 생성이 선행되며, 이 단계가 실패하면 취소 POST는 보내지지 않습니다.
  • 브로커가 정정/취소할 수량이 없습니다를 반환하면, runtime은 이것도 즉시 최종 CANCELLED나 단순 실패로 확정하지 않습니다. 대신 cancel_request_status=\"broker_zero_qty_pending\", submit_status=\"CANCEL_RECONCILE_PENDING\" 중간 상태를 남기고 이후 reconcile로 최종 CANCELLED 또는 FILLED/PARTIALLY_FILLED를 확정합니다.
  • 따라서 정정 후 취소처럼 broker 주문번호가 여러 번 바뀌어도, 운영 표면은 마지막 broker id만 따로 떼어 읽지 말고 같은 주문의 연속된 상태 변경으로 해석해야 합니다.

원격 모니터/전략 화면

cloud relay를 통한 원격 monitor/control 화면도 별도 손익 공식을 쓰지 않습니다.

  • 원격 스냅샷은 로컬 콘솔과 같은 account_summary 계산 필드를 전달합니다.
  • 전략 목록도 같은 runtime_summary를 전달합니다.
  • 원격 워크스페이스/종목 행도 같은 workspace_rows[] 필드를 전달합니다.

즉, 로컬 대시보드와 원격 모니터에서 같은 계좌/전략을 보고 있다면 realized_pnl, unrealized_pnl, total_pnl, pending_count, holding 같은 핵심 숫자는 같은 의미로 해석해야 합니다.

거래 탭 차트 기본 scale

거래 탭의 차트는 더 이상 무조건 고정 5T로 시작하지 않습니다.

  • 전략 스크립트에 chart("1T"), chart("5T") 같은 선언이 있으면 그 선언된 scale을 우선 사용합니다.
  • 선언된 scale이 없을 때만 기본 fallback scale을 사용합니다.
  • chart("60T")chart("1H")는 같은 hourly class로 취급합니다. 따라서 거래 탭의 차트 정렬, preload/backfill, 추가 스크롤 로딩도 둘을 같은 hourly 규칙으로 계산합니다.

이 변경으로 line/pane overlay가 특정 scale에만 그려지는 전략은, 거래 탭에서도 작성자가 의도한 차트 타임프레임에서 바로 보이게 됩니다.

엔진 중지와 차트 평가

엔진 STOPPED는 더 이상 “차트/조건식/평가가 모두 멈춘다”는 뜻이 아닙니다.

  • 조건검색 refresh, live price/candle 집계, DSL 평가, overlay 갱신은 계속 유지됩니다.
  • 실제 주문 제출만 멈춥니다.
  • 따라서 엔진을 멈춘 상태에서도 거래 탭 차트는 최신 candle과 overlay를 계속 반영할 수 있습니다.
  • 다만 주문 의도가 확정돼도 제출되지 않은 경우에는 회색 preview marker로만 보일 수 있습니다.

전략 편집의 스크립트 고정

기존 전략은 편집 패널에서 다른 스크립트로 교체할 수 없습니다.

  • 새 전략 생성 중에는 스크립트를 선택할 수 있습니다.
  • 이미 생성된 전략을 편집할 때는 스크립트 변경 버튼이 비활성화됩니다.
  • 툴팁으로 전략의 스크립트는 변경할 수 없습니다 안내가 표시됩니다.

차트 snapshot 재사용

거래 탭은 backend runtime snapshot을 그대로 매번 새로 요청하는 대신, 브라우저 세션 동안 최근 본 전략-종목-scale 차트를 재사용합니다.

  • 같은 종목을 다시 펼치거나 탭을 오갈 때는 이미 받은 candle/overlay snapshot을 우선 사용합니다.
  • 거래 탭은 기본 정렬 상단 종목 family를 background preload로 먼저 채워, 자주 보게 되는 종목은 펼치기 전에 메모리 cache가 준비될 수 있습니다.
  • 브라우저는 현재 열어 둔 화면과 무관하게 runtime tracked symbol universe 전체 이벤트를 계속 수신할 수 있습니다.
  • cache는 최근 본 종목 family(strategy + 종목) 단위 LRU로 유지하고, family 안의 여러 scale snapshot은 함께 보존됩니다.
  • cache miss가 나면 backend /api/chart 최신 snapshot으로 즉시 복구합니다.
  • cache family는 단순 idle 시간보다 websocket reconnect/gap 같은 resync evidence가 생겼을 때 stale로 판단하고, 다음 열람 시 backend snapshot으로 background refresh 해 다시 맞춥니다.
  • backend snapshot은 runtime seed와 WebSocket 집계를 반영한 최신 상태를 기준으로 합니다.

거래 탭 종목 행 확장

거래 탭은 종목 행을 더 이상 한 번에 하나만 펼치도록 제한하지 않습니다.

  • 여러 종목을 동시에 펼쳐 차트와 overlay를 비교할 수 있습니다.
  • 이미 펼쳐봤던 종목은 같은 세션 cache를 재사용하므로, 접었다가 다시 열 때 매번 처음부터 다시 불러오지 않습니다.

bar_status 주의

  • c.bar_status는 현재 구현에서 항상 "completed"입니다.

관련 문서