튜토리얼약 13분 읽기

자동매매 여러 전략 동시 운영 — 자본 배분과 신호 충돌 방지 설계

전략 하나로는 만족이 안 되는 시점이 온다

전략 하나를 만들고 백테스팅과 실전을 거치다 보면 자연스럽게 다음 질문이 따라온다. "이 전략은 특정 국면에서만 잘 먹히는데, 다른 국면을 커버할 전략을 하나 더 붙이면 어떨까." 추세추종 전략 하나만 돌리다가 횡보장에서 계속 손실을 보면, 평균회귀 전략을 하나 더 추가하고 싶어지는 식이다.

문제는 여기서부터다. 전략이 하나일 때는 "이 계좌의 자금을 이 전략이 다 쓴다"는 전제가 자동으로 성립했다. 전략이 둘 이상이 되는 순간 그 전제가 깨진다. 각 전략이 얼마씩 쓸 수 있는지, 두 전략이 같은 종목을 동시에 건드리면 무슨 일이 벌어지는지를 새로 설계해야 한다. 이 글은 그 설계, 즉 자본 배분신호 충돌 방지를 다룬다.

구체적인 배분 비율이나 실제 운영 중인 전략 조합은 밝히지 않는다. 여러 전략을 같은 계좌에서 굴릴 때 반드시 세워야 하는 구조를 개념과 의사코드 수준에서 정리한다.

전략별 자본을 어떻게 나누는가 — 고정 배분과 성과 기반 배분

여러 전략을 한 계좌에서 돌린다는 건, 전체 자본을 전략 단위로 쪼개서 각 전략에게 "너는 이 범위 안에서만 매매해라"라고 선을 긋는다는 뜻이다. 이 선이 없으면 전략 A가 오늘 낸 신호와 전략 B가 오늘 낸 신호가 같은 잔고를 두고 경쟁하게 된다.

가장 단순한 방법은 고정 배분이다. 전략마다 전체 자본의 일정 비율을 미리 정해두고, 그 비율만큼의 하위 계좌가 있는 것처럼 다룬다.

python 코드 보기
def allocate_fixed(total_equity: float, strategy_weights: dict) -> dict:
    """전략별 고정 비율로 자본을 나눈다. strategy_weights 합은 1.0 이하."""
    return {
        strategy_id: total_equity * weight
        for strategy_id, weight in strategy_weights.items()
    }

고정 배분은 단순하고 예측 가능하지만, 성과가 나쁜 전략에도 계속 같은 비율의 자본이 묶인다는 단점이 있다. 이를 보완하는 게 성과 기반 배분이다. 일정 주기마다 최근 성과를 반영해 배분 비율 자체를 조정하는 방식이다.

python 코드 보기
def allocate_by_performance(total_equity: float, strategy_scores: dict, min_weight: float) -> dict:
    """최근 성과 점수(score)가 높을수록 더 많은 자본을 배정하되, 최소 배분(min_weight)은 보장한다."""
    total_score = sum(max(score, 0) for score in strategy_scores.values())
    allocations = {}

    for strategy_id, score in strategy_scores.items():
        raw_weight = (max(score, 0) / total_score) if total_score > 0 else 0
        weight = max(raw_weight, min_weight)
        allocations[strategy_id] = total_equity * weight

    return allocations

여기서 중요한 건 min_weight로 최소 배분을 보장한다는 점이다. 성과가 일시적으로 나쁘다고 자본을 0으로 만들어버리면, 그 전략이 다시 좋은 국면을 만나도 자본이 없어 기회를 못 잡는다. 성과 기반 배분은 비중을 조절하는 것이지, 특정 전략을 완전히 퇴출시키는 로직이 아니다. 포트폴리오 리밸런싱 로직 설계에서 다룬 "목표비중과 허용오차" 개념을 전략 단위로 옮겨온 것과 비슷하다고 보면 된다 — 종목 대신 전략이 리밸런싱 대상이 되는 셈이다.

같은 종목에 신호가 겹칠 때 — 충돌의 두 가지 형태

전략별 자본을 나눴어도 충돌은 남는다. 자본은 전략마다 분리했지만, 매매 대상 종목까지 분리되는 건 아니기 때문이다. 전략 A와 전략 B가 둘 다 같은 종목을 다룬다면 충돌이 생길 수 있다.

충돌은 크게 두 형태로 나타난다.

첫째, 중복 매수다. 전략 A와 전략 B가 같은 종목에 대해 동시에 매수 신호를 낸다. 각자의 배분 자본 안에서는 정상적인 주문이지만, 합쳐놓고 보면 그 종목 하나에 예상보다 훨씬 많은 자금이 쏠린다.

둘째, 상반된 신호다. 전략 A는 매수를, 전략 B는 매도를 같은 종목에 대해 동시에 낸다. 둘 다 그대로 실행하면 사실상 제로섬으로 수수료만 나가는 매매가 되거나, 실행 순서에 따라 원치 않는 포지션이 만들어진다.

이 두 형태를 막으려면 두 가지 장치가 필요하다. 하나는 우선순위, 하나는 락(lock)이다.

python 코드 보기
def resolve_signal_conflict(symbol: str, pending_signals: list[dict], strategy_priority: dict) -> dict | None:
    """같은 종목에 여러 전략의 신호가 몰렸을 때 하나만 통과시킨다.
    pending_signals: [{"strategy_id": ..., "action": "BUY"/"SELL", ...}, ...]
    """
    if not pending_signals:
        return None

    actions = {s["action"] for s in pending_signals}
    if len(actions) > 1:
        # 상반된 신호 — 이번 틱은 아무것도 실행하지 않고 다음 판단으로 넘긴다
        return None

    # 방향이 같으면(중복 매수 등) 우선순위가 가장 높은 전략의 신호만 통과
    pending_signals.sort(key=lambda s: strategy_priority.get(s["strategy_id"], 0), reverse=True)
    return pending_signals[0]

strategy_priority는 어떤 전략의 신호를 더 신뢰할지를 미리 정해둔 값이다(예: 검증 기간이 더 긴 전략, 리스크가 더 낮은 전략을 우선시). 상반된 신호가 들어오면 이 예시처럼 "아무것도 안 한다"를 기본값으로 두는 게 안전하다. 둘 중 하나를 임의로 골라 실행하는 것보다, 판단을 보류하고 사람이나 상위 로직이 다시 볼 기회를 남기는 편이 사고를 줄인다.

우선순위만으로는 부족한 지점도 있다. 여러 전략이 동시에 같은 종목의 주문을 넣으려는 순간, 판단 로직이 끝나기도 전에 주문이 먼저 나가버릴 수 있다. 이걸 막는 게 종목 단위 이다. 한 종목에 대한 주문 판단이 진행 중이면, 그 종목에 대한 다른 전략의 판단은 대기시키거나 이번 틱에서는 건너뛴다.

python 코드 보기
symbol_locks = set()

def try_place_order(symbol: str, strategy_id: str, order_fn):
    """종목 단위 락 — 이미 처리 중인 종목이면 주문을 건너뛴다."""
    if symbol in symbol_locks:
        return False  # 다른 전략이 이 종목을 처리 중

    symbol_locks.add(symbol)
    try:
        order_fn()
        return True
    finally:
        symbol_locks.remove(symbol)

락은 "동시에 두 주문이 같은 종목에 대해 경쟁하는 상황" 자체를 차단하는 장치이고, 우선순위는 "그 경쟁에서 누가 이길지"를 정하는 규칙이다. 둘은 역할이 다르므로 하나만 두면 안 된다.

전략별 리스크 한도는 전체 포트폴리오 한도에 종속되어야 한다

자동매매 리스크 관리 규칙 설계에서 손절·포지션 한도·서킷브레이커를 다뤘다. 전략이 하나일 때는 이 한도들이 곧 계좌 전체의 한도였다. 전략이 여러 개가 되면 이야기가 달라진다. 전략마다 각자의 포지션 한도를 잘 지키고 있어도, 전략들을 합친 전체 노출은 한도를 넘을 수 있다.

예를 들어 전략 A와 전략 B가 각각 "한 종목에 전체 자본의 10%까지"라는 한도를 지킨다고 해도, 둘 다 같은 종목에 10%씩 배정하면 그 종목의 실제 비중은 20%가 된다. 전략 단위로만 한도를 체크하면 이런 경우를 못 잡는다.

그래서 리스크 한도는 두 층으로 나눠야 한다. 전략별 한도는 그 전략에게 배분된 자본 안에서의 한도이고, 그 위에 전체 포트폴리오 기준으로 종목별·전체 노출을 다시 한번 체크하는 상위 계층이 있어야 한다.

python 코드 보기
def can_open_position_portfolio_level(symbol: str, order_value: float, strategy_id: str,
                                       strategy_allocations: dict, all_positions_by_symbol: dict,
                                       total_equity: float, max_symbol_pct_of_total: float) -> bool:
    """전략별 한도를 통과한 주문이라도, 전체 계좌 기준 종목 집중도를 다시 체크한다."""
    existing_symbol_value = all_positions_by_symbol.get(symbol, 0)
    projected_total = (existing_symbol_value + order_value) / total_equity

    if projected_total > max_symbol_pct_of_total:
        return False
    return True

이 함수는 전략별 포지션 한도 체크(008에서 다룬 can_open_position과 같은 성격의 함수)를 통과한 주문에 대해 한 번 더 실행되는 상위 게이트다. 순서가 중요하다 — 전략별 체크가 먼저, 전체 포트폴리오 체크가 그다음이다. 전략은 자기 자본 안에서 자유롭게 판단하되, 최종 실행 직전에는 반드시 전체 계좌 관점의 문지기를 거치게 만든다. 이 구조가 없으면 개별 전략은 모두 "규칙대로 하고 있다"고 믿는 채로 전체 계좌만 위험해지는 상황이 생긴다.

자주 하는 실수

  1. 전략별 자본을 나누지 않고 공유 잔고를 그냥 같이 쓴다. 배분 로직 없이 여러 전략이 같은 계좌 잔고를 조회해서 각자 주문 가능 금액을 계산하면, 두 전략이 동시에 신호를 낸 순간 실제 가용 자금보다 많은 주문이 나갈 수 있다. 전략을 추가하기 전에 반드시 자본부터 나눠야 한다.
  2. 상관관계가 높은 전략을 묶어놓고 "분산"이라고 착각한다. 추세추종 전략 두 개를 이름만 다르게 운영하면, 둘 다 같은 시장 국면에서 같은 방향으로 손실을 낸다. 진짜 분산은 서로 다른 국면에서 강한 전략을 묶는 것이지, 전략 개수를 늘리는 것 자체가 아니다.
  3. 신호 충돌 처리 순서를 안 정해서 나중에 실행된 전략이 이긴다. 우선순위 없이 코드 실행 순서(등록 순서, 반복문 순서)에 따라 결과가 갈리면, 같은 상황에서도 그날그날 다른 결과가 나올 수 있다. 우선순위는 코드가 아니라 설계 단계에서 명시적으로 정해야 한다.
  4. 전략별 리스크 한도만 두고 전체 포트폴리오 한도를 안 둔다. 앞서 다룬 것처럼 개별 전략은 각자 한도를 지켜도 합산 노출은 초과할 수 있다. 전략 단위 체크와 전체 계좌 단위 체크는 항상 한 쌍으로 있어야 한다.

오늘의 정리

  1. 전략이 하나에서 여러 개로 늘어나는 순간, "계좌 자금을 전략 하나가 다 쓴다"는 전제가 깨지므로 자본 배분 로직을 새로 설계해야 한다.
  2. 자본 배분은 전략마다 고정 비율을 주는 방식과, 최근 성과에 따라 비중을 조정하는 방식이 있으며 후자도 최소 배분은 보장해야 한다.
  3. 같은 종목에 여러 전략의 신호가 겹치는 충돌은 우선순위(누구 신호를 따를지)와 락(동시 처리 자체를 막을지)이라는 서로 다른 역할의 두 장치로 막는다.
  4. 전략별 리스크 한도는 전체 포트폴리오 한도의 하위 개념이어야 한다 — 개별 전략이 규칙을 지켜도 합산하면 초과할 수 있으므로 상위 계층의 체크가 별도로 필요하다.
  5. 전략 개수를 늘리는 것 자체는 분산이 아니다. 자본 배분·충돌 방지·상위 리스크 한도라는 구조가 갖춰졌을 때만 "여러 전략 운영"이 의미가 있다.

본문 코드는 개념 설명을 위한 의사코드이며 실제 거래 시스템의 구현이 아닙니다. 이 글은 투자 조언이 아니고 특정 종목이나 매매기법을 추천하지 않으며, 전략을 여러 개로 늘린다고 수익이 보장되지 않습니다. 투자 손실의 책임은 투자자 본인에게 있습니다.

← 전체 글 목록