파이썬으로 자동매매 만들기 · 6편

규칙 기반 매매를 코드로 — 그리드 tier 로직

떨어지면 사고 오르면 파는 그리드 매매를 tier 포인터로 구현. 상태 영속(SQLite)과 로직/통신 분리 설계.

이제 "주문을 넣고 확인하는 도구"는 갖췄다. 여기에 매매 규칙을 얹으면 자동매매가 완성된다. 이 강좌가 다루는 규칙은 그리드 매매다 — 감정이 아니라 정해둔 격자(grid) 대로 사고파는 방식.

⚠️ 그리드는 하나의 예시 전략일 뿐이다. 이 글은 전략을 코드 구조로 어떻게 옮기는가(엔지니어링)를 다루며, 특정 종목·시점의 매수·매도를 권하지 않는다. 원금 손실 위험이 있으며 수익을 보장하지 않는다.

핵심 개념 — tier 포인터

  • 종목 1개 = 그리드 1개 = 활성 tier 포인터 1개.
  • 가격 격자를 tier로 나눈다. 지금 내가 서 있는 칸이 활성 tier.
  • 매 사이클: 한 칸 아래 tier에 매수 주문, 현재 tier에 매도 주문을 건다.
  • 매수 체결 → 더 싸게 더 샀으니 포인터가 tier+1로 내려간다.
  • 매도 체결 → 비싸게 팔았으니 포인터가 tier-1로 올라간다.

즉 "떨어지면 사고, 오르면 판다"를 가격 칸 단위로 자동 반복하는 것이다.

상태 전이 예시

[그리드 활성화]
    ↓ 시장가 매수 (현재가)
[tier 1  curr=$600, qty=4]
    ↓ 주가 하락, $594 매수 체결
[tier 2  curr=$594, qty=8, 매도대기 4]
    ↓ 주가 상승, $599.94 매도 체결
[tier 1 복귀  curr=$600, qty=4]   ← 히스토리에서 복원

두 가지 특수 규칙

  • tier 1 sliding: 주가가 계속 오르면(매도 목표가 초과) 매도하지 않고 기준가(curr_amount)를 위로 끌어올린다. 상승장에서 너무 일찍 팔고 빠지는 걸 막는다.
  • Jackpot(복리): 매도로 생긴 순이익(수수료·세금 반영)을 쌓아두고, 다음 매수 때 "1주 더 살 수 있으면" 수량을 +1 한다. 이익을 다시 굴리는 복리 장치.

상태는 반드시 저장한다 — SQLite

자동매매의 상태(활성 tier, 보유 수량, 진입가, 누적 이익, 대기 주문)는 메모리에만 두면 안 된다. 앱이 꺼지거나 재시작되면 다 날아간다.

그래서 상태를 SQLite에 영속화한다. 재시작하면 DB에서 정확히 그 자리로 복원된다. (이 프로젝트는 stocks / positions / trades / jackpots / grid_pending 같은 테이블로 나눈다.)

내 주문 vs 손으로 낸 주문 — 체결 분류

계좌에는 그리드가 낸 주문뿐 아니라 내가 손으로 낸 주문도 섞일 수 있다. 체결내역을 읽을 때 어떤 체결이 그리드 것인지 분류해야 포인터를 올바르게 움직인다. (주문번호·가격·수량을 대기 주문과 대조해서 매칭한다.)

설계 교훈 — 로직과 통신을 분리하라

이 프로젝트의 가장 큰 힘은 여기 있다. 그리드 로직(GridManager)은 "컨트롤러"에게 "이 종목 현재가 알려줘 / 이 주문 넣어줘" 라고만 말한다. 그 컨트롤러가 GUI를 클릭하든 REST를 호출하든 로직은 한 줄도 모른다.

GridManager (규칙)
   └─ Controller (인터페이스: get_current_price / submit_order / ...)
         ├─ 예전: GUI 자동화 버전
         └─ 지금: REST 버전   ← 심장만 갈아끼움

덕분에 GUI에서 REST로 넘어올 때 매매 로직을 재검증할 필요가 없었다. "규칙"과 "통신"을 분리하는 이 습관이, 오래 살아남는 자동매매의 비결이다.

📦 코드로 — SQLite 스키마 + tier 전이

핵심 두 테이블. tier_templates는 각 tier의 규칙(하락률·상승률), positions는 현재 상태다.

CREATE TABLE tier_templates (      -- 각 tier의 규칙
    code TEXT, tier INTEGER,
    down_rt REAL,                  -- 아래 tier까지 하락률(%) → 매수가
    up_rt   REAL,                  -- 위로 상승률(%) → 매도가
    PRIMARY KEY (code, tier)
);
CREATE TABLE positions (           -- 현재/과거 포지션 (활성 1개 + 이력)
    seq INTEGER PRIMARY KEY AUTOINCREMENT,
    code TEXT, tier INTEGER,
    curr_amount REAL,              -- 이 tier 진입가
    curr_qt INTEGER,              -- 누적 보유수량
    buy_amount REAL, sell_amount REAL,   -- 다음 매수가/매도가
    sell_qt INTEGER,             -- 이 tier에서 매도할 수량
    yn_use INTEGER               -- 1이면 활성 포지션
);

매수 체결 → tier+1 (핵심만 발췌):

def apply_buy_fill(repo, code, fill_price, fill_qty):
    prev = repo.get_active_position(code)          # 지금 활성 tier
    new_tier = prev.tier + 1
    tmpl = repo.get_tier_template(code, new_tier)   # 다음 tier 규칙
    repo.deactivate_positions(code)                 # 이전 활성 끄기
    repo.insert_position(
        code=code, tier=new_tier,
        curr_amount=fill_price,                     # 방금 산 가격이 새 기준
        curr_qt=prev.curr_qt + fill_qty,           # 누적 수량 증가
        buy_amount=fill_price * (1 - tmpl.down_rt/100),  # 더 내려가면 또 매수
        sell_amount=fill_price * (1 + tmpl.up_rt/100),   # 오르면 매도
        sell_qt=fill_qty, yn_use=1,
    )

매도 체결 → tier-1 + 이익 기록 (핵심만):

def apply_sell_fill(repo, code, fill_price, fill_qty, fee, tax):
    prev = repo.get_active_position(code)
    new_tier = prev.tier - 1
    profit = calculate_profit(prev.curr_amount, fill_price, fill_qty, fee, tax)
    repo.upsert_jackpot(code, repo.get_jackpot(code) + profit.net)  # 복리 적립
    lower = repo.find_most_recent_position_at_tier(code, new_tier)  # 이력 복원
    repo.deactivate_positions(code)
    repo.insert_position(
        code=code, tier=new_tier,
        curr_amount=lower.curr_amount,             # 원래 아래 tier 값으로 복귀
        curr_qt=prev.curr_qt - fill_qty,
        sell_qt=lower.sell_qt, yn_use=1,
    )
    return profit

실제 프로젝트는 GridRepository(CRUD 계층)로 나뉘어 있고, tier 간격이 붕괴하면 템플릿으로 재계산하는 가드까지 있다. 하지만 그리드의 심장은 위 두 함수 — 매수하면 내려가고, 매도하면 올라오고, 이익은 복리로 쌓는다.

📦 돌아가는 미니 그리드 — 복붙 실행

위 로직을 외부 의존 없이 눈으로 확인하는 자체 완결 스크립트다. mini_grid.py로 저장하고 실행하면 tier가 오르내리는 게 보인다. (증권사·API 필요 없음 — 순수 SQLite)

# mini_grid.py — 그리드 tier 전이를 SQLite 로 시뮬레이션
import sqlite3

def new_db():
    db = sqlite3.connect(":memory:")
    db.execute("CREATE TABLE pos(id INTEGER PRIMARY KEY AUTOINCREMENT,"
               " tier INT, curr REAL, qty INT)")
    db.execute("INSERT INTO pos(tier,curr,qty) VALUES(1, 600.0, 4)")  # 시작
    return db

def active(db):                       # 현재 활성 포지션 = 가장 최근 행
    return db.execute("SELECT tier,curr,qty FROM pos "
                      "ORDER BY id DESC LIMIT 1").fetchone()

def buy_fill(db, price, qty):          # 매수 체결 → tier+1 (더 싸게 더 샀다)
    tier, _, prev_qty = active(db)
    db.execute("INSERT INTO pos(tier,curr,qty) VALUES(?,?,?)",
               (tier + 1, price, prev_qty + qty))
    print(f"  매수 {qty}주 @ ${price} → tier {tier+1}")

def sell_fill(db, price, qty):         # 매도 체결 → tier-1 (비싸게 팔았다)
    tier, curr, prev_qty = active(db)
    profit = (price - curr) * qty      # 실현이익 (수수료·세금은 생략)
    lower = db.execute("SELECT curr FROM pos WHERE tier=? "
                       "ORDER BY id DESC LIMIT 1", (tier - 1,)).fetchone()
    db.execute("INSERT INTO pos(tier,curr,qty) VALUES(?,?,?)",
               (tier - 1, lower[0] if lower else curr, prev_qty - qty))
    print(f"  매도 {qty}주 @ ${price} → tier {tier-1} (실현이익 ${profit:.2f})")

if __name__ == "__main__":
    db = new_db()
    print("시작 :", active(db))         # (1, 600.0, 4)
    buy_fill(db, 594.0, 4)              # 하락 → 매수 체결
    print("현재 :", active(db))         # (2, 594.0, 8)
    sell_fill(db, 599.94, 4)            # 상승 → 매도 체결
    print("현재 :", active(db))         # (1, 600.0, 4) 복귀

실제 프로젝트는 여기에 tier_templates(하락률·상승률), jackpot 복리, tier 간격 붕괴 가드가 더해질 뿐 — 핵심 전이는 이 세 함수가 전부다.

✅ 확인

python mini_grid.py
시작 : (1, 600.0, 4)
  매수 4주 @ $594.0 → tier 2
현재 : (2, 594.0, 8)
  매도 4주 @ $599.94 → tier 1 (실현이익 $23.76)
현재 : (1, 600.0, 4)

tier가 1→2(매수)→1(매도) 로 오르내리고 실현이익이 찍히면 그리드 심장을 이해한 것이다. 여기에 4·5편의 실제 시세·주문을 연결하면 진짜 그리드가 된다.

다음 편 예고

그리드가 "돈 버는 로직"이라면, 마지막 7편은 "안 죽고 안 터지게" 운영하는 법이다 — 드라이런, 수량 대조(reconciliation), 다단계 드로다운 알림, 긴급 정지, 텔레그램·대시보드. 사실 자동매매의 진짜 난이도는 여기에 있다.


그리드 로직에서 헷갈리는 부분(tier 전이·sliding·복리)이 있으면 댓글로 물어봐 주세요. 상태 전이는 그림으로 그려보면 훨씬 쉬워요.

핵심 주장 확인 자료 · 검토 기준 2026-08-27

아래 자료에서 본인에게 적용되는 최신 조건을 다시 확인하세요. 링크는 특정 상품 가입을 권하는 제휴 링크가 아닙니다.

이 글은 교육·정보 제공 목적이며 개인별 투자자문이나 특정 금융상품의 매수·매도 권유가 아닙니다.

함께 생각해요

이 글을 읽고 생긴 질문이나 직접 실행한 경험을 나눠주세요.

댓글을 불러오는 중입니다…