멀티계정 관제 아키텍처 — 여러 창 · 여러 브로커 계정 동시/독립 운용
2026-07-31 · KPS 트레이딩 도구 · 설계 + 골격 구현 대상: Tradovate 계정 다수 (APEX 연동 / Lucid 연동 / LifeUp 연동) 이번 산출 범위 = 설계 + 한선씨 골격 + 자체시험. 실주문·신규 인스턴스 기동은 범위 밖(사장님 승인 사항).
0. 현재 도달점 (실측 기준선)
- 크라우니AI 브라우저(WKWebView) 1인스턴스(:9847) 가 Tradovate 웹에서 LifeUp 5계좌 조작
- 단일계좌 실증 5거래 완주 2회 / 5계좌 동시 진입·진출 1사이클 성공(진입 5/5 · 진출 5/5 · 잔존 0)
- 손익 정본 = 브로커 실현손익 델타. 규칙엔진은 3거래마다 4상 고도화
- 현 규칙셋 수익성 없음 (최근 실증 -$7.50 / 5거래) → 전략도구로 채택/폐기를 판정 가능하게 만드는 것이 이번 설계의 목적 중 하나
1. 인스턴스 모델
크라우니AI 인스턴스 1개 = 창 1개 = 탭 1개 = 브로커 로그인 세션 1개 = CROWNY_CTL_PORT 1개
│
└─ 그 로그인 아래의 계좌 N개(LifeUp 5계좌처럼)는 "계좌전환" 클릭으로 순차 조작
- 제어서버 계약(정본
/Users/ef/CrownyBrowser/native/crowny-ai-control.m):
/health /navigate /eval /find /click_text /screenshot /read_page /tabs /console
CROWNY_CTL=1 또는 CROWNY_CTL_PORT 지정 시에만 리슨(기본 off)CROWNY_CTL_TOKEN 설정 시 X-Crowny-Token 헤더 강제(멀티 인스턴스에서는 인스턴스별 토큰 권장)/Users/ef/CrownyBrowser/크라우니AI제어.app/Contents/MacOS/launcher)가 이미 멀티 인스턴스에 필요한 요소를 전부 갖고 있다:bash PORT="${CROWNY_CTL_PORT:-9847}"
URL="${CROWNY_CTL_URL:-https://trader.tradovate.com}"
# 같은 포트로 이미 살아있으면 새로 안 띄움(중복 방지)
export CROWNY_CTL=1 CROWNY_CTL_PORT="$PORT"
→ 런처 수정 없이 CROWNY_CTL_PORT=9848 launcher 형태로 두 번째 인스턴스를 띄울 수 있다.
단 /tmp/crowny-ai-ctl.port 단일 파일에 마지막 포트를 덮어쓰는 부분은 멀티에서 의미를 잃으므로,
포트의 단일 원본은 이 문서의 레지스트리 PSV로 승격한다.1.1 왜 인스턴스를 분리하는가 (실측 근거)
| 근거 | 내용 |
|---|---|
| 쿠키 단위 세션 충돌 | Tradovate 웹은 브라우저 쿠키/로컬스토리지 1벌에 로그인 1세션. 같은 WKWebView 인스턴스(공유 데이터스토어)에서 두 번째 계정으로 로그인하면 첫 세션이 로그아웃된다. 계정 2개 동시 유지 불가. |
| DOM 계약이 "현재 로그인" 전제 | 브라우저어댑터.한선 의 상태읽기()·계좌전환() 은 li.selected[data-testid^=account-item-] 등 화면 1개를 읽는다. 한 창에 두 브로커를 얹으면 이 계약이 성립하지 않는다. |
| 세션 4상 판정의 단위 | 세션감시.한선 은 POSITION+MNQ+선택계좌 유무로 티/옴/타/음을 낸다 — 이 판정 자체가 인스턴스 1개 단위다. 멀티 계정을 한 창에 넣으면 경보 대상이 특정 불가. |
| 실패 격리 | 한 브로커 세션이 만료·리다이렉트돼도 다른 브로커는 영향받지 않아야 한다. 프로세스 분리가 가장 싼 격리. |
확인된 사실: 제어서버는 이미 인스턴스별 포트를 지원하고, 런처는 이미 포트별 중복기동을 막는다. 즉 멀티 인스턴스는 새 기능 구현이 아니라 "관제 계층"만 추가하면 되는 상태다.
1.2 포트 할당 규약
- 이 포트들은 로컬 전용 제어포트(127.0.0.1 바인드, 게이트웨이 도메인 아님).
crowny-ports.sh check로 충돌만 확인했고,set(gateway.yaml 변경)은 하지 않았다 — 공유 인프라 무접촉 원칙.
| 포트 | 상태(2026-07-31 실측) | 배정안 |
|---|---|---|
| 9847 | 사용 중 (PID 94980 CrownyBrowser, 사장님 로그인 세션) | LifeUp (기존, 종료·네비게이션 금지) |
| 9848 | 미등록·비어 있음 | APEX |
| 9849 | 미등록·비어 있음 | Lucid |
| 9850 | 불가 — monitoring/defence/monitor.crowny.org 점유 | 사용 금지 |
| 9851 | 불가 — churck.crowny.org | 사용 금지 |
| 9852 | 불가 — crowny-ai.crowny.org | 사용 금지 |
| 9853~9856 | 미등록·비어 있음 | 예비(4번째~7번째 인스턴스) |
2. 레지스트리 (단일 원본)
파일: /tmp/kps/인스턴스.psv — 1줄 = 1인스턴스
인스턴스ID|포트|브로커|계정군|전략ID|상태|비고
| 필드 | 의미 |
|---|---|
| 인스턴스ID | INST_LIFEUP 등 고유 식별자. 로그·패널·플래그 키 |
| 포트 | CROWNY_CTL_PORT |
| 브로커 | LifeUp / APEX / Lucid |
| 계정군 | 혼합 팬아웃의 그룹 키(예 LIFEUP5, APEX, LUCID) |
| 전략ID | 전략.psv 의 전략 참조. 미배정 가능 |
| 상태 | 4상 — 1=티(가동) 0=옴(대기/미확인) -1=타(정지, 팬아웃 대상 제외) -9=음(무응답) |
| 비고 | 자유 텍스트(구분자·개행 자동 안전화) |
INST_LIFEUP|9847|LifeUp|LIFEUP5|S_기본|1|사장님 로그인 세션 - 종료금지
INST_APEX|9848|APEX|APEX|미배정|0|미기동 - 사장님 승인 후 런처 기동
INST_LUCID|9849|Lucid|LUCID|미배정|0|미기동 - 사장님 승인 후 런처 기동
3. 팬아웃 3모드
| 모드 | 대상 선정 | 용도 |
|---|---|---|
| 동시 | 상태≠타(정지) 인 전 인스턴스 | 하나의 시그널을 전 브로커에 동시 진입/진출. 기존 다계좌동시.한선 의 5계좌 사이클을 인스턴스 축으로 한 단계 확장 |
| 독립 | 전 인스턴스를 열거만 하고 명령을 주입하지 않음 | 인스턴스별로 배정된 전략이 각자 판단·실행. 관제기는 헬스·집계·안전정지만 담당 |
| 혼합 | 계정군 == 그룹 인 인스턴스만 | "APEX 그룹만 청산", "LIFEUP5만 관망" 같은 그룹 단위 지시 |
- 실패 격리: 루프는 인스턴스 실패에서 끊기지 않는다.
{대상, 성공, 실패}로 집계 반환 + 인스턴스별 로그. - 상태
-1(타/정지)인 인스턴스는 모든 모드에서 자동 제외 → 사장님이 특정 창을 손으로 쓰는 동안 관제 제외 가능.
실측 드라이런 (2026-07-31)
[관제팬아웃] 모드=동시 그룹= 명령=ENTER|ALL|매수|지정가|0|SYMBOL=MNQU6
[관제팬아웃] 대상=3 성공=0 실패=3 ← KJ_LIVE 미설정 → fail-closed 로 전부 거부(의도된 결과)
[관제팬아웃] 모드=혼합 그룹=LIFEUP5 명령=STATUS|ALL
[관제팬아웃] 대상=1 성공=1 실패=0 ← 읽기전용 STATUS 는 통과, position=0 확인
[관제팬아웃] 모드=독립
[관제팬아웃] 대상=3 성공=3 실패=0 ← 명령주입 없이 열거만
4. 안전 설계 (최우선)
4.1 실주문 3중 게이트 (fail-closed)
DRY_RUN활성— 브라우저어댑터 전역, 기본 1. 1이면 주문/청산 실호출 없음(기록만)KJ_LIVE환경변수 —"1"이 아니면 주문계열 명령(ENTER/COPY/CLOSE_POSITION/CANCEL) 자체를 거부BA_미니차단(심볼)— MNQ 계열 외 전부 차단. DRY_RUN 무관 항상 동작
/health, 조회용 /eval)는 게이트와 무관하게 허용 — 무해하고, 안전 판단에 필수이기 때문.4.2 전체청산 (최우선 안전 기능)
전체청산() 은 전 인스턴스를 순회하며:
/health로 생존 확인 → 죽어 있으면 "무응답" 집계하고 다음 인스턴스로 계속CLOSE_POSITION|ALL요청 (게이트 통과 시에만 실호출, 아니면 드라이런 기록)/eval로POSITION재판독 →0이면 검증통과0이 아니거나 판독 실패(NA)면 인스턴스별 잔존 경보
/tmp/kps/포지션잔존_<포트>.flag ← 인스턴스별 플래그 (기존 단일 포지션잔존.flag 와 별도)
+ 세션감시.한선 의 세션경보_포지션잔존() 재사용(macOS 알림·로그 채널 그대로).4.3 세션 4상
인스턴스별로 T/A/O/U 를 판정해 세션_티_정상(1)/세션_타_만료(-1)/세션_옴_전환중(0)/세션_음_무응답(-9) 로 매핑.
판정 JS는 세션감시.한선 의 것과 동일 계약. (전역 BA_제어포트 를 함수 안에서 갈아끼우면
한선씨 "전역 스칼라 함수내 재대입" 함정에 걸리므로, 관제기는 포트를 인자로 받는 자체 호출 경로를 쓴다.)
5. 전략도구
파일: /Users/ef/CrownyDoc/projects/kps-trading-tool/전략도구.한선
전략 = 규칙셋 + 파라미터 묶음. /tmp/kps/전략.psv
전략ID|이름|규칙셋경로|목표포인트|손절포인트|쿨다운초|최대거래|일한도센트|상태|비고
규칙셋경로는 기존 M8 규칙 PSV 를 가리킨다(서브5-전략엔진-v4.한선 / 실증실행기.한선 스키마 그대로):
id|이름|슬롯|비교연산|기준값|가중치|상태|발화수|승기여|패기여|손익합
전략도구는 규칙을 경로로만 참조한다 — M8_규칙파싱 을 재정의하지 않아 파일간 함수명 충돌(하드에러)을 원천 회피.
- 파라미터는 실증실행기의 하드 상수(
EX_목표포인트=12,EX_손절포인트=8,EX_쿨다운초=20,EX_거래캡=5,EX_일목표상한센트=6000)와 1:1 대응 → 전략별로 이 값들을 바꿔 A/B 가능.
전략평가 — 4상 판정
입력 = 장부배열(거래ID|시그널|발화IDs|손익센트|손익유효), 손익유효==1 인 줄만 사용(실현손익 델타 미확보분 = 오염원 제외).
| 지표 | 산식 |
|---|---|
| 승률 | 승 × 100 / 표본 (정수 %) |
| 손익합 | 실현손익 델타 합(센트) |
| 거래당평균 | 손익합 / 표본 |
| 기대값 | (승합/표본) − (패합/표본) |
| MDD | 누적곡선 최고점 대비 최대 낙폭(센트) |
| 판정 | 조건 |
|---|---|
| 음(-9) 데이터부족 | 표본 < 10 |
| 타(-1) 폐기 | 손익합 < 0 |
| 티(1) 채택 | 손익합 > 0 그리고 승률 ≥ 45% 그리고 MDD ≤ 손익합×3 |
| 옴(0) 관찰 | 그 외(손익합=0 포함) |
현 규칙셋(-$7.50/5거래)을 이 판정에 넣으면 표본 5 → 음(데이터부족). 즉 "실패"가 아니라 "아직 판정할 표본이 없다"가 정직한 결론이다. 최소 10거래가 판정 기준선.
모든 나눗셈은 ST_나눔()(절단 나눗셈)으로 — 한선씨 자연반올림(|r|≤|b|/2)으로 음수 손익이 왜곡되는 함정 회피(자체시험으로 검증).
6. 멘토패널 (설계 + 데이터 계약 — UI 구현은 이번 범위 밖)
6.1 목적
사람이 도구 상태를 한눈에 보고 개입하는 화면. 자동화가 사람을 대체하는 게 아니라, 사람이 전체청산·전략교체·인스턴스 정지를 즉시 누를 수 있게 하는 것이 목적.
6.2 재사용 가능한 기존 자산 (조사 결과)
crowny-trader:7741 에 이미 KPS 전용 자산이 있다:
| 자산 | 경로 | 재사용 판정 |
|---|---|---|
| KPS 화면 | /Users/ef/crowny-trader/public/kps.html (226줄) | 재사용 가능 — 카드/표 레이아웃 골격 그대로 인스턴스 카드로 전환 |
| SSE 스트림 | /Users/ef/crowny-trader/routes/kps.js GET /api/kps/stream | 재사용 가능 — kpsClients 집합이 autotrade SSE와 완전 분리돼 있고, ~25s 주석 핑 하트비트로 프록시 idle 끊김·죽은 소켓 정리까지 이미 구현됨 |
| 기존 엔드포인트 | /api/kps/livelock /edge /condition /journal /nt8 /config /mentor-trade /mentor-edge | 부분 재사용 — 이미 mentor-* 네이밍이 있어 멘토 개념이 선점돼 있음. 인스턴스 축은 신규 /api/kps/instances 로 추가하는 편이 충돌이 없다 |
6.3 데이터 계약 (JSON)
관제기가 /tmp/kps/멘토패널.json 을 주기 갱신 → trader 서버는 이 파일을 읽어 SSE 로 밀기만 한다.
폴링 부하 최소화: 브라우저는 폴링하지 않고 SSE 수신만, 서버는 파일 mtime 변화 시에만 브로드캐스트.
json{
"갱신시각": 1785475099,
"인스턴스수": 3,
"인스턴스": [
{
"인스턴스ID": "INST_LIFEUP",
"포트": 9847,
"브로커": "LifeUp",
"계정군": "LIFEUP5",
"계정수": 5,
"생존": 1,
"세션상태": 1,
"세션라벨": "티(로그인정상)",
"url": "https://trader.tradovate.com/",
"포지션": "0",
"전략ID": "S_기본",
"상태": 1,
"최근거래": []
}
]
}
최근거래= 인스턴스별/tmp/kps/거래_<인스턴스ID>.psv의 마지막 3줄(장부 PSV 원문 문자열 배열)당일실현손익은 현재 계약에 미포함 — 인스턴스별 일일 손익 누적기가 아직 없기 때문(§8 미해결).
"당일실현손익센트" 로 추가.
경보도 미포함 — 현재는 파일 플래그(포지션잔존_<포트>.flag)가 정본. 패널은 이 파일 존재 여부로 표시하면 되고,
6.4 갱신 주기 제안
| 상황 | 주기 |
|---|---|
| 장중·포지션 보유 | 5초 |
| 장중·무포지션 | 15초 |
| 장외 | 60초 또는 수동 |
멘토패널갱신() 1회 비용 = 인스턴스당 /health 1 + /eval 2(세션·포지션). 3인스턴스 기준 9회 로컬 HTTP.
전부 --connect-timeout 2 -m 6 이라 죽은 인스턴스도 최악 2초에 끊긴다.7. 사장님이 결정해야 할 항목
- 포트 확정 — 9848=APEX, 9849=Lucid 로 확정할지. (9850~9852는 다른 서비스 점유로 사용 불가, 예비는 9853~9856)
- 포트 정식 등록 여부 —
crowny-ports.sh set은 gateway.yaml(공유 인프라)을 건드리므로 실행하지 않았다. 로컬 예약 목적으로 등록할지 결정 필요. - 브로커별 계정 수 — LifeUp은 5계좌 실측 확정. APEX·Lucid는 각 로그인 아래 계좌가 몇 개인지 미확인. 계좌 ID 목록은 인스턴스별로 달라야 하는데 현재
BA_계좌목록은 LifeUp 5개가 하드코딩돼 있다(§8-1). - 인스턴스 기동 승인 — 새 크라우니AI 인스턴스는 띄우지 않았다. 기동 =
CROWNY_CTL_PORT=9848 /Users/ef/CrownyBrowser/크라우니AI제어.app/Contents/MacOS/launcher+ 사장님이 직접 브로커 로그인(자동 로그인 시도 금지). - 전략 배정 — 인스턴스별로 같은 전략(동시 모드)인지, 각기 다른 전략(독립 모드)인지. 현 규칙셋은 표본 부족으로 미검증 상태라, 초기엔 한 인스턴스에서만 라이브 + 나머지는 드라이런 병주(shadow) 로 비교하는 편이 안전.
- 제어토큰 — 인스턴스가 늘면
CROWNY_CTL_TOKEN을 인스턴스별로 다르게 줄지(오발사 방지 효과).
8. 미해결 / 다음 작업
- 계좌목록 인스턴스별 분리 —
브라우저어댑터.한선BA_초기화()가 LifeUp 5계좌를 하드코딩. 멀티 브로커에는
/tmp/kps/계좌_<인스턴스ID>.psv 같은 인스턴스별 계좌 파일이 필요하다. (어댑터 수정 필요 — 이번 범위 밖)
- 어댑터의 포트 파라미터화 —
BA_제어포트가 전역 상수 9847. 관제기는 자체 호출 경로로 우회했지만,
CTL_PORT 환경변수를 읽거나, 인스턴스별 어댑터 프로세스로 분리해야 한다.
현재 KJ_인스턴스명령() 의 주문 경로는 게이트 통과 시 "위임" 로그만 남기는 미배선 스텁이다(허위배선 방지 — 실호출하는 척하지 않음).
- 인스턴스별 일일 실현손익 누적 — 멘토패널
당일실현손익필드의 전제. 실증실행기의EX_일일손익센트를 인스턴스 축으로 확장 필요. - 전략도구 ↔ 실증실행기 배선 — 현재 실증실행기는 파라미터를 파일 상수로 갖는다.
전략.psv에서 읽어오도록 배선하면 전략 교체가 무재컴파일로 가능. - 레지스트리 4095/문자열 캡 — 인스턴스 수가 수십 개가 될 일은 없어 현재 구조로 충분(캡 무관).
- 멘토패널 UI — 이번 범위 밖. §6.2 자산 위에 인스턴스 카드 + 전체청산 버튼부터.
9. 산출 파일
| 파일 | 내용 |
|---|---|
/Users/ef/CrownyDoc/projects/kps-trading-tool/관제기.한선 | 레지스트리·전체헬스·관제팬아웃·전체청산·전략배정·멘토패널갱신 |
/Users/ef/CrownyDoc/projects/kps-trading-tool/전략도구.한선 | 전략 CRUD + 4상 전략평가(승률·기대값·MDD·거래당평균) |
/Users/ef/CrownyDoc/projects/2026-07-31-멀티계정-관제-아키텍처.md | 본 문서 |
실행
bashcd /Users/ef/CrownyOS/crownyc
# 관제기 (BA_SKIP_RUN=1 필수 — 어댑터 top-level 배치처리 억제)
./hanseonc_high /Users/ef/CrownyDoc/projects/kps-trading-tool/관제기.한선 > /tmp/kj.toau
env BA_SKIP_RUN=1 KJ_TEST=1 ./crownyc run /tmp/kj.toau # 자체시험 12/12 (무네트워크)
env BA_SKIP_RUN=1 KJ_HEALTH=1 ./crownyc run /tmp/kj.toau # 읽기전용 전체헬스
env BA_SKIP_RUN=1 KJ_PANEL=1 ./crownyc run /tmp/kj.toau # 멘토패널.json 갱신
# 전략도구 (독립 — 네트워크·브라우저 무접촉)
./hanseonc_high /Users/ef/CrownyDoc/projects/kps-trading-tool/전략도구.한선 > /tmp/st.toau
env ST_TEST=1 ./crownyc run /tmp/st.toau # 자체시험 10/10
검증 결과 (2026-07-31 실측)
| 항목 | 결과 |
|---|---|
| 관제기 컴파일 | CROWNY_STRICT=1 경고 0, 49915 큐브 |
| 관제기 자체시험 | 12 / 12 PASS |
| 전략도구 컴파일 | CROWNY_STRICT=1 경고 0, 11384 큐브 |
| 전략도구 자체시험 | 10 / 10 PASS |
| 라이브 읽기전용 헬스 | 9847 생존 · 세션 티(로그인정상) · position=0 / 9848·9849 음(무응답, 미기동) |
| 실주문 | 0건 — DRY_RUN=1 · KJ_LIVE 미설정으로 주문계열 전부 fail-closed 거부 확인 |
| 9847 인스턴스 | 종료·네비게이션 없음(POST /health, 조회용 /eval 만 호출) |