Joyuno's Dev Blog

2026-06-26 (금) 개발일지

dev-blog

오늘은 두 가지 이슈를 파고들었다. 하나는 bit-scalp 봇이 리팩토링 후 포지션 진입을 멈춘 원인 추적, 다른 하나는 demo_apps Android Compose 화면에서 아이콘만 렌더링되고 전체 UI가 안 나오는 문제였다. 전자는 시그널 로직은 살아있는데 게이트가 막고 있었고, 후자는 Compose와 View의 래스터 방식 차이를 몰랐던 게 원인이었다.

bit-scalp 봇 진입 차단 — 시그널은 살아있는데 왜 안 들어가나

어제 리팩토링 + TP/SL·트레일링 수치를 바꾼 뒤부터 V2 봇이 포지션 진입을 전혀 안 하고 있었다. 11시간 라이브 로그를 보니 시그널 스캔 자체는 정상 — 10:01:14 기준 19 candidates 중 WLDUSDT(5/5) 같은 상위 시그널이 잘 잡히고 있었다. 뭔가 게이트 로직이 문제일 거라 생각했다.

첫 가설은 내가 리팩토링하면서 orchestrator.py의 진입 조건을 잘못 건드렸을 거라는 것. 근처 continue 문이나 mode 조건문을 뒤졌다. grep으로 ‘진입 차단’ 관련 로그 문자열을 찾아보니 "TRANSITION" 모드에서 찍히는 [MODE-BLOCK] 로그만 보였다. 시그널 방향 자체는 정상이었다. 그제서야 의심을 바꿨다 — 봇이 진입을 못 한 게 아니라 의도적으로 막고 있었던 것.

진짜 원인은 orchestrator.py:659-673 블록이었다. if self.current_mode == "TRANSITION": 에서 continue로 진입을 차단하고 있었고, MEAN_REVERT 모드에서는 시그널 방향이 sell일 때만 진입 가능했다. 근데 라이브 모드는 TRANSITION → MEAN_REVERT로 바뀐 상태였고, ADX=21.3, BBW_chg=-22.4%로 전환 조건이 맞아떨어진 것. 즉 봇은 의도한 대로 움직이고 있었고, 내가 리팩토링 전에도 같은 조건이었는지 착각한 게 문제였다.

가설 진실
리팩토링이 시그널 로직을 깨뜨렸다 시그널 100% 정상, grep으로 확인
TP/SL 수치 변경이 진입 조건을 막는다 TP/SL은 진입 이후 단계, 영향 0
mode 게이트가 잘못됐다 정확히 의도대로 동작 중 — TRANSITION은 진입 차단, MEAN_REVERT는 sell만 허용

해결은 간단했다 — 코드 수정이 아니라 내가 모드 전환 조건을 잊고 있었다는 걸 인지한 것. 리팩토링 영향이 0건임을 라이브 로그로 확인했고, 시그널 로직·게이트 로직 모두 정상임을 재확인했다. 수정한 파일은 하나도 없다.

검증: 오늘 10:03까지의 라이브 흐름을 추적했다. 17:05 TRANSITION 진입 → 22:57 TRANSITION 유지 → 23:02 MEAN_REVERT 전환 — 이 모든 게 리팩토링 전에도 동일한 조건이었다. 봇 둘 다 11시간 정상 가동 중.

버린 옵션: 리팩토링 직후라 orchestrator.py 전체 diff를 보려 했지만, continue 문 하나만 바꿔도 진입이 막힐 수 있어 코드만 보고 단정하지 않았다. 로그 기반으로 추적한 게 옳았다.

남은 과제: MEAN_REVERT 모드에서 sell 시그널만 받는 게 전략적으로 맞는지는 추가 검토 필요. 일단 의도대로 동작 중이니 시그널 통계를 일주일 더 모은 후 판단.

demo_apps Android Compose 화면 — 아이콘만 뜨고 UI가 안 나오는 이유

http://<INTERNAL_IP>:9080/experiments/mobile-rum/replay?session=s_43bdf31d082444 에서 Android 세션을 리플레이해보니, 홈 화면이 아니라 아이콘들만 프레임에 떠 있고 정작 home 화면 UI(텍스트·버튼·레이아웃)는 전혀 안 보였다. iOS 시뮬레이터는 정상 작동 중이었다.

처음엔 VPN을 의심했다. macOS WireGuard가 “Connected” 상태지만 ping 100% 손실 — 데이터플레인이 끊겨서 뷰어에 직접 들어가볼 수 없었다. 하지만 iOS가 같은 네트워크에서 정상인 걸 보고 네트워크 문제는 아닌 게 확실해졌다.

다음 가설은 Android 빌드에 문제가 있다는 것. 방금 1100ms 짜리 설틀 빌드(pid 7043)가 성공했고, autoDemo로 자동 화면 이동까지 완료된 상태였다. 빌드 문제도 아니다. 그제서야 코드 레벨을 파보기 시작했다.

핵심 발견은 OmnitraceMobile.kt의 래스터 구조였다. Compose 화면의 아이콘은 동기 인라인 래스터(rasterizeComposeIconNodes)로 처리되고, 이는 풀 스냅샷에 포함된다. 반면 비동기 증분 래스터(PendingRaster)는 View 기반 ImageView 전용으로, Compose에는 ImageView 자체가 없다. Android Compose 홈 화면은 구조적으로 아이콘 노드만 프레임에 잡히고 나머지 Composable 요소(텍스트·버튼 등)는 스냅샷에서 누락되는 설계였다.

항목 Compose View 기반
아이콘 래스터 rasterizeComposeIconNodes (동기 인라인, 풀 스냅샷) PendingRaster (비동기 증분)
텍스트/버튼 스냅샷 누락 정상 캡처
원인 Compose에 ImageView 없음 → 비동기 증분 래스터 미동작 정상 작동

해결은 즉각적인 코드 수정이 아니라 구조적 판단이다. iOS는 Compose를 안 쓰니까 정상인 것. Android Compose를 View 기반으로 마이그레이션하거나, ComposeView 오버레이 방식을 도입해야 한다. 지금은 코드만으로 확인한 상태고, VPN이 복구되면 실제 리플레이에서 Compose 트리와 View 트리가 어떻게 분리되는지 직접 눈으로 확인할 예정이다.

남은 과제: RasterizeComposeIconNodes가 아이콘만 캡처하고 나머지를 놓치는 정확한 조건을 역추적해야 한다. Compose의 drawWithContent 훅을 가로채는 방법으로 대안 테스트를 계획 중.

오늘의 한줄

시그널이 안 잡히는 건 코드가 고장난 게 아니라 게이트가 의도대로 움직이고 있을 수도 있고, Compose 화면이 안 나오는 건 래스터 방식 자체가 다르다는 걸 몰랐기 때문이다 — 가설을 검증하려면 로그와 설계를 함께 봐야 한다.