QuantDinger 격자 최적점 둘을 같은 시각에 나란히 걸어 봤습니다 — 그리고 대조를 못 세웠습니다

실측 비용을 넣으면 격자 최적점이 60/180 에서 120/360 으로 밀립니다. 그 둘을 같은 호가에 함께 걸어 보려다 막힌 기록과, 무엇이 있어야 그 대조가 서는지입니다.

QuantDinger 격자 최적점 둘을 같은 시각에 나란히 걸어 봤습니다 — 그리고 대조를 못 세웠습니다

이 글의 완료 조건: 독자가 자기 도구의 설정 두 개를 같은 시각·같은 호가에 한 칸만 바꿔 걸어 보고, 그 대조가 「설 수 있는 것인지 아닌지」를 먼저 판정할 수 있으면 성공입니다.

하는 일 도구가 고른 설정 둘을 같은 시각·같은 호가에 한 칸만 바꿔 함께 발주하고, 차이가 그 칸에서만 나오게 만듭니다
맞는 사람 파라미터를 바꿔 가며 격자를 돌리는데, 그 답이 실시세에서도 갈리는지 재 본 적이 없는 사람
안 맞는 사람 도구가 거래별 진입·청산 레벨을 안 내놓는 경우. 이 글이 바로 그 벽에 부딪힌 기록입니다
이 글의 결과물 그대로 도는 레시피 한 벌, 그리고 대조가 안 선 이유와 무엇이 있어야 서는지
준비물·비용 코드 0원 · 모델 비용 0원 · weltiq pro 계정 (시험 주문 월 1,000회)
시간·난도 20분 · 중 (curl·JSON·bash)
weltiq 와의 관계 이 글이 전부 그 이야기입니다 — 절차 글이라 5절이 따로 없습니다
검증 환경·날짜 2026-09-28 (KST) · 격리 서버 · 합성 호가 (5절에 그 한계를 그대로 적었습니다)
제휴 고지 필자는 weltiq 로부터 이 연재의 원고료를 받습니다. 이번 편은 실패한 대조가 본론이고, 그 사실을 성공담으로 바꾸지 않았습니다

사람 쪽 문장은 실제로 친 명령을 대화로 옮긴 것이고, 출력은 실측이며 숫자는 손대지 않았습니다.


1. 이 도구가 남기던 빈칸

14편에서 QuantDinger 의 백테스트 격자를 돌리고, 우리 쪽 체결 기록에서 뽑은 실측 비용(수수료 0.0010 · 슬리피지 0.0001)을 그 격자에 되먹였습니다. 그랬더니 답이 바뀌었습니다.

격자 비용 가정 수익률 거래 PF
60 / 180 가정 비용 (수수료 0.0005 · 슬리피지 0.0005) -5.344065708001111 68 0.5123854981235674
120 / 360 실측 비용 (수수료 0.0010 · 슬리피지 0.0001) -1.9871885445930992 33 0.678789397136766

비용이 오르면 최적점이 더 느린 쪽으로 밀립니다. 이게 14편에서 제일 값어치 있던 한 줄이었습니다.

그래서 이번에 하려던 일은 간단했습니다. 그 두 최적점을 같은 시각·같은 호가에 함께 걸어서, 실시세에서도 정말 갈리는지 보는 것.

못 했습니다. 두 군데서 막혔습니다

① 격자 행에 거래가 없습니다. 격자 결과(40-qd-grid-cp0.json · 40-qd-grid-cp1.json)의 rows 에 들어 있는 것은 이게 전부입니다.

fast · slow · totalReturn · totalTrades · totalCommission · profitFactor · maxDrawdown · finalEquity

거래별 진입·청산 레벨이 한 줄도 없습니다. 그래서 "두 최적점이 같은 봉에서 어떻게 갈리는가"를 이 기록으로 답할 수 없습니다. 68거래와 33거래가 있었다는 것만 알고, 그 68과 33이 어디서 났는지를 모릅니다.

② 기간쌍은 시험 주문에 들어가는 칸이 아닙니다. 시험 주문 요청 본문(TrialBuyReq)에 fast·slow 같은 칸이 없습니다. 있어도 안 됩니다 — 우리 규칙 문법의 기간 상한이 200 이라 360 을 못 받습니다. 14편에서 이미 받아 둔 거절이 있습니다.

422 {"code":"invalid_rules","detail":"period_limit"}

그래서 무엇이 있어야 이 대조가 서나 — 행마다 trades[]

격자 한 줄에 거래 목록이 붙어 있으면 됩니다. 이런 모양입니다.

{"fast": 60, "slow": 180, "totalReturn": -5.344…,
 "trades": [{"entry_time": "…", "entry_price": "…", "exit_time": "…",
             "exit_price": "…", "direction": "long", "exit_reason": "…"}, …]}

이게 있으면 두 최적점의 진입 레벨을 같은 시각에 나란히 낼 수 있습니다. 어려운 요구가 아닙니다 — 15편 Moss는 자기 백테스트 결과에 trades[] 를 실제로 내놓습니다(21-moss-own-backtest-30d.json). QuantDinger 의 /backtest 결과는 안 내놓습니다. 그 한 칸 차이가 「이 도구로 반사실 대조를 세울 수 있는가」를 가릅니다.

백테스트 센터 — 격자 여섯 판이 UI 에 그대로 쌓여 있습니다

오른쪽 Recent backtest runs 를 보세요. 한 줄이 격자 한 칸이고, 보이는 것은 수익률 하나입니다. 이 목록 어디를 눌러도 「이 설정이 몇 시 몇 분에 얼마에 들어갔나」는 나오지 않습니다. 이 글이 막힌 자리가 이 화면입니다.


2. 왜 같은 시각에 함께 내야 하나

시험 주문의 첫 용도는 2편에 썼습니다 — 도구가 낸 판단 하나를 끝까지 재는 것. 두 번째 용도가 이 글입니다.

같은 시각·같은 호가에 한 칸만 바꿔 내면, 차이가 그 칸에서만 나옵니다. 그게 전부입니다. 시차를 두고 두 번 돌리면 호가가 달라져서, 갈린 결과가 설정 때문인지 시장 때문인지 영원히 못 가립니다.

이게 가능한 이유는 잔고를 안 건드리기 때문입니다. 계좌가 움직이면 비교군 두 번째 팔을 낼 자본이 없고, 낸다 해도 첫 팔이 자본을 묶어 두 번째 팔의 조건이 달라집니다. 재는 행위가 재려는 대상을 망가뜨립니다. 시험 원장은 거기서 빠져 있으니 비교군을 몇 개든 낼 수 있습니다.

실제로 몇 개까지 되나

한 종목·한 호가에 변형 6개를 동시 발주해 봤습니다.

결과 6/6 전부 201. 실패·거절 0
소요 0.02초 (6건 전체)
접수 시각 분포 6.5 밀리초 안에 전부
막힌 것 없습니다. 멱등 충돌·한도·중복 어느 코드도 안 났습니다

상한을 정하는 것은 월 한도뿐입니다. 그리고 그 한도는 그냥 나눗셈입니다.

소모
대조군 N팔 N회
같은 client_order_id · 같은 본문 재전송 0회 (200 멱등 재생)
같은 client_order_id · 다른 본문 0회 (409 client_order_id_conflict)
청산 체결 0회 (체결 기록의 counted: false)

재시도가 공짜라서 실수해도 예산이 안 샙니다. pro 월 1,000회면 2팔짜리 대조는 월 500회, 3팔은 333회, 4팔은 250회입니다.


3. 레시피 — 그대로 도는 것

전체는 11-trial-order-quantdinger-grid-ab.sh 에 있습니다. 네 칸으로 돕니다.

① 심기 — 한 칸만 다른 두 팔. 두 팔의 client_tracking_id 를 같게 두고, metadata.arm 으로 구분합니다.

BODY=$(jq -n --arg px "$PX" --arg sl "$STOP_PX" --arg a "$ARM" \
  '{venue:"binance-spot", symbol:"BTCUSDT", side:"long",
    type:"limit", limit_price:$px,                    # ← 이 한 칸만 다릅니다
    client_tracking_id:"qd-grid-ab-1",                # ← 두 팔 같습니다
    exit_plan:{stop_loss:$sl, close_after_seconds:45},
    metadata:{arm:$a, changed_field:"limit_price"}}')  # ← base / variant

손절은 두 팔을 같은 값으로 묶었습니다. 보호 손절 5% 는 두 설정이 같은데 진입가가 달라서 손절선이 갈립니다. 한 칸만 남기려고 국면 호가 기준 85900 × 0.95 = 81605.00 하나로 고정했습니다.

의도는 도구가 갖습니다. 격자 순위·PF·거래 수는 서버로 안 보냅니다. 서버가 갖는 것은 집행 기록이고, 둘을 잇는 것은 추적 ID 하나입니다.

client_order_id,tracking,arm,grid,profit_factor,total_trades,cost_basis
qd-grid-ab-1-60-180,qd-grid-ab-1,base,60/180,0.5123854981235674,68,assumed
qd-grid-ab-1-120-360,qd-grid-ab-1,variant,120/360,0.678789397136766,33,measured

② 회수 — ?tracking=. 추적 ID 정확 일치 필터입니다. 내 계정 안에서만 돕니다.

curl -sS "$BASE/v1/forward-tests?tracking=$RUN_ID&limit=200"

③ 전체 CSV — 한 번에. 계정 전체 주문 엔드포인트가 같은 필터를 받습니다.

curl -sS "$BASE/v1/forward-tests/orders?tracking=$RUN_ID&format=csv"

종전에는 회수가 「목록 1회 + 시험마다 내보내기 1회」였습니다. 한 달 한도를 다 쓴 달이면 1,001번입니다. 열은 시험 하나 내보내기와 같아서 두 CSV 를 이어붙여도 안 깨집니다.

④ 조인 — awk 를 쓰지 마세요. CSV 의 metadata·exit_plan·tags 칸에 인용된 쉼표가 들어 있습니다. -F, 로 자르면 열이 밀리는데, 에러 없이 조용히 밀려서 더 나쁩니다. 인라인 python3 의 csv 모듈로 조인합니다.

레시피 안에 주석으로 박아 둔 것 셋

틱 반올림. 우리 기록의 실제 진입 체결가는 85979.70711050 과 85795.14865650 입니다. 그대로 limit_price 에 넣으면 이렇습니다.

422 invalid_limit_price "px 85979.70711050 is not a multiple of tick 0.01"

서버가 조용히 반올림해 주지 않습니다. 조건 가격도 실제 주문과 같은 격자에 있어야 하고, 사용자가 준 가격을 서버가 몰래 바꾸면 그게 더 위험합니다. 그래서 호출하는 쪽에서 버립니다 — 롱 지정가는 내리는 쪽이 보수적이라 올림이 아니라 버림입니다. 85979.70 / 85795.14.

metadata 에 float 를 넣지 마세요. 결정론 때문에 막혀 있습니다. 확률을 싣는 것은 흔한 쓰임이라 안내가 같이 옵니다.

422 invalid_metadata "metadata 에 float 를 쓸 수 없습니다 (결정론) — 숫자는 문자열로 보내세요 예: {"p_up": "0.55"}"

float_paths 에 어느 칸인지 적혀 옵니다. 문자열로 보내면 통과합니다.

자동 청산이 추적 ID 를 물려받습니다. 실현 손익은 청산 행에 붙습니다. 종전에는 그 행의 추적 ID 가 비어 있어서, ?tracking= 으로 회수하면 진입만 오고 손익이 빠졌습니다. 지금은 진입의 추적 ID·태그를 청산이 물려받습니다.


4. 돌려 본 결과

환경 고지. 아래는 격리 임시 서버 · 합성 호가에서 나온 값입니다. 운영이 아니고, 여기 적힌 손익은 시장 사실이 아닙니다 — 제가 넣은 고정 호가 위의 산술입니다. 여기서 재는 것은 대조 설계가 서는가 하나입니다.

두 팔을 국면 호가 85900.00 / 85900.01 위에 13:26:45.882 와 .889 에 던졌습니다. 7밀리초 차이입니다.

팔 격자 지정가 상태 진입 체결가 청산가 close_reason 수수료 순손익 소요
base 60/180 85979.70 closed 85908.6050005 85891.4049995 close_after 0.01374400 −0.015120000080 46.0초
variant 120/360 85795.14 entering 미체결 — 없음 0 null —

갈렸습니다. 체결 여부에서

같은 호가(ask 85900.01)에서 85979.70 은 즉시 체결되고 85795.14 는 호가에 걸린 채 남았습니다.

그리고 여기가 이 글에서 제일 값어치 있는 대목입니다.

미체결 팔은 close_after_seconds 시계가 시작조차 하지 않습니다.

상대 시한의 기준이 「첫 진입 체결의 공개 시각」이라, 안 채워지면 시계가 안 돕니다. max_duration_hours 가 될 때까지 그대로 남습니다. 관측 창 45초 안에서 비교군은 아무 결과도 안 냈습니다 — 손익도, 수수료도, 청산 사유도, 소요 시간도 없습니다.

안 채워진 팔은 비교에서 빠집니다. 이건 우리 쪽 버그가 아니라 계약대로입니다(있지도 않은 체결을 지어내는 쪽이 훨씬 나쁩니다). 다만 대조 설계를 짤 때 이걸 모르면 표가 한 칸 비어 있는 이유를 영원히 못 찾습니다. 지정가로 대조군을 낼 거면 두 팔이 전부 체결될 호가대인지 먼저 확인하세요. 아니면 둘 다 시장가로 내고 다른 칸을 바꾸는 편이 낫습니다.

우리 쪽 상세 — 한 대조의 두 팔이 같은 추적 ID 아래 쌓여 있습니다

카드 두 장의 제목 줄을 비교해 보세요. 메모만 「기준안 손절 −0.05%」와 「비교군 손절 −0.20%」로 다르고, 그 아래 추적 ID: tool-a-run-42 와 ab-compare 태그가 두 팔에 똑같이 붙어 있습니다. 한 칸만 바꾼 대조가 화면에서는 이렇게 보입니다. (이 화면의 두 팔은 둘 다 체결됐습니다. 위 표의 비교군처럼 안 채워지면 진입·청산·순손익 칸이 통째로 빕니다.)

같은 호가였다는 증거

접수 시점의 기준가가 두 팔 모두 85900.005 로 같았습니다(qty_basis.reference_px). 다른 대조에서는 더 센 증거가 나왔습니다 — 세 팔짜리 대조에서 진입 체결의 published_at 이 13:25:57.507 로 밀리초까지 같았고, 네 팔짜리에서도 13:29:40.456~.457 로 4밀리초 안에 들어왔습니다. 한 번 공개된 호가 하나가 여러 건을 전부 채웠다는 뜻입니다.

이번 편에서는 그 증거를 못 씁니다. 한 팔이 안 채워졌으니 짝지을 published_at 이 없습니다.

시험 매수 화면 — 한 실험의 팔들이 목록에 나란히 섭니다

맨 위 8 / 1000 건 이 이번 달 소모입니다 — 팔 하나가 1건입니다. 아래 「내 비교」에 카드가 줄줄이 쌓여 있고, 카드마다 ab-compare 태그와 같은 추적 ID 가 붙어 있습니다. 여기서 test_id 를 뽑아 CSV 로 내리는 것이 레시피 ②③ 입니다.

한도는 예상대로

2팔 = 2회. 계좌 원장 스냅샷은 시작 전과 끝난 뒤가 완전히 동일했습니다 — 8개 계좌·거래 표 전부 한 행도 안 움직였습니다.


5. 이 조합의 한계

① 격자 파라미터 대조는 안 섰습니다. 이 글의 결론입니다. 제가 대조한 것은 「두 설정이 우리 쪽에서 실제로 낸 진입가」이지 격자 그 자체가 아닙니다. 60/180 과 120/360 이 실시세에서 어떻게 갈리는지는 이 방법으로 답이 안 나옵니다. 도구가 거래별 레벨을 안 내놓고, 우리 규칙 문법 상한이 200 이라서입니다.

② 미체결 팔은 자기가 본 호가를 기록에 안 남깁니다. 접수 응답에 bid/ask 가 따로 안 나옵니다. reference_px 는 중간값 하나입니다. 그래서 「이 팔은 어떤 호가를 보고 안 채워졌나」를 응답 어디서도 못 읽습니다. 열려야 할 자리입니다.

③ 호가 스냅샷 식별자가 없습니다. BBO 한 건에 id 가 없어서 「이 두 체결이 같은 한 호가에서 나왔다」를 응답만으로 단정할 수 없습니다. published_at 이 밀리초까지 같다는 것이 현재의 최선이고, 그건 정황이지 식별이 아닙니다.

④ 합성 호가입니다. 위 손익은 시장 사실이 아닙니다. 확인된 것은 배선과 계약이고 거래소 성능이나 실제 비용 실증이 아닙니다.

⑤ 회수 경로에 정정이 하나 있습니다. 이 실측을 돌릴 때는 GET /v1/forward-tests?q=<추적 ID> 가 0건을 돌려줬습니다 — 그때의 q 는 test_id·symbol·memo·tags 만 봤고 client_tracking_id 를 안 봤습니다. 그래서 실제로 실험을 되찾은 것은 태그였습니다(?tag=a2-04-04 → 정확히 3건).

그 칸이 이 실행 직후에 열렸습니다 — ?tracking= 정확 일치 필터와 q 의 추적 ID 확장. 3절 레시피는 그 새 계약을 씁니다. 다만 저는 그 코드를 돌리지 않았습니다. 위 「0건」은 열리기 전의 사실이고, 열린 뒤가 어떤지는 이 글이 실측으로 주장하지 않습니다. 레시피를 처음 돌리실 때 ?tracking= 이 몇 건을 돌려주는지 직접 확인하세요.


6. 언제 쓰고 언제 쓰지 마라

쓸 때

쓰지 말 때

그리고 억지로 성공담을 만들지 마세요. 이번 편에서 제가 낸 두 팔은 실제로 갈렸지만, 갈린 이유는 격자가 아니라 지정가입니다. 그걸 "격자 최적점이 갈렸다"고 쓰면 거짓말입니다. 대조가 안 선다는 판정도 결과이고, 무엇이 있어야 서는지를 아는 게 독자에게 더 값어치 있습니다. 그 한 칸은 격자 행마다 붙은 trades[] 입니다.


이 글의 증거: 실측 로그 · 원자료 · 레시피. 위 숫자는 전부 그 기록에서 가져왔고, 못 세운 대조와 5절의 한계도 같은 기록에 있습니다.