전략 개발 가이드

AvalonQuant 키트에서 내 전략을 만드는 방법. 노코드 빌더 사용법, 파이썬 코드 전략 형식과 ctx API 레퍼런스, 지표 함수, 손절·익절 옵션, 백테스트 해석, 예제 3개와 실제 실행 결과, 자주 하는 실수, 샌드박스와 API 키 보안을 다룹니다.

키트 출시 예정: 키트 다운로드는 아직 열리지 않았습니다(준비 중). 이 문서는 출시될 키트의 '전략 만들기' 기능을 미리 설명합니다. 교육용 자료이며 투자 권유가 아니고, 어떤 성과도 보장하지 않습니다.

시작하기

AvalonQuant 키트는 내 PC 에서 돌아가는 자동매매 도구입니다. 전략을 만들고, 과거 시세로 백테스트하고, 가상거래나 실거래로 실행합니다. 실행 방식은 실거래와 키트의 가상거래 두 가지뿐입니다.

1. 키트 설치 (출시 예정)

키트는 출시 예정입니다. 다운로드가 열리면 /downloads 에서 받아 설치 스크립트(macOS install.command, Windows install.bat)를 실행합니다. 설치가 끝나면 브라우저에서 내 PC 의 대시보드가 열립니다.

2. 시세 조회용 API 키 연결

가상거래도 실제 시세가 필요합니다. 계좌 연결 화면에 거래소·증권사의 라이브 API 키를 시세 조회 전용(읽기 권한만)으로 넣어야 가상거래를 시작할 수 있습니다. 가상거래에서는 주문이 나가지 않고, 키트가 실제 시세로 체결을 계산합니다. 출금 권한은 어떤 경우에도 필요 없습니다.

3. 첫 전략 만들기

  • 왼쪽 메뉴의 '전략 만들기'를 엽니다.
  • '예시로 채우기'에서 '이동평균 교차'를 골라 채웁니다(학습용 예시, 성과 보장 아님).
  • '검증'으로 빈 조건·범위 오류가 없는지 확인하고 '저장'합니다.
  • '백테스트'로 과거 성과와 학습·검증 구간을 확인합니다.
  • 전략 화면에서 '실행 대상'을 체크하면 가상거래로 돌아갑니다. 실거래는 가상거래로 충분히 확인한 뒤 따로 켭니다.

이용권

무료: 전략 만들기와 백테스트는 제한이 없고, 실행은 가상거래 1개까지입니다. 유료: 이용권 등급의 슬롯 수만큼 동시에 실행합니다(가상거래·실거래). 노코드 전략과 코드 전략은 같은 슬롯을 씁니다.

노코드 빌더 사용법

노코드 탭에서는 코드 없이 블록을 조립합니다. 신호는 봉이 마감된 뒤 종가 기준으로 내고, 주문은 다음 봉 시가에 체결됩니다. 백테스트와 가상거래·실거래가 같은 규칙 실행기를 씁니다.

전략 만들기 화면. 1 이름과 예시, 2 종목 선정(시장·거래소·특정 종목 또는 거래대금 상위 N)부터 차례로 채웁니다.
전략 만들기 화면. 1 이름과 예시, 2 종목 선정(시장·거래소·특정 종목 또는 거래대금 상위 N)부터 차례로 채웁니다.

조건 블록

블록 하나는 [지표(파라미터)] [비교] [값 또는 다른 지표]입니다. 비교는 보다 큼·보다 작음·이상·이하·상향 교차·하향 교차 여섯 가지이고, AND/OR 묶음을 한 단계까지 넣을 수 있습니다.

RSI 과매도 반등 예시: RSI(14)가 30을 상향 교차하면 매수, RSI(14) > 70 이면 매도. 손절 8%·최대 보유 20봉을 체크했습니다.
RSI 과매도 반등 예시: RSI(14)가 30을 상향 교차하면 매수, RSI(14) > 70 이면 매도. 손절 8%·최대 보유 20봉을 체크했습니다.

매도와 위험 관리

매도 조건 블록과 함께 손절·익절·트레일링·최대 보유 기간을 체크할 수 있습니다. 모두 종가로 판단하고 다음 봉에 팝니다. 자금 관리에서는 1회 비중, 분할 매수, 최대 동시 종목, 리밸런싱 주기를 정합니다. 1회 비중 × 최대 동시 종목이 100%를 넘을 수 없습니다(현금 주문만).

저장 전 검증

'검증'은 빈 조건, 자기 자신과의 비교, 매수와 똑같은 매도 조건(순환), 범위 밖 값, 비중 합 초과, 지표 창 한도를 한 번에 알려 줍니다. 문제가 없으면 전략이 한 문장으로 요약됩니다.

봉 단위(지금은 일봉)와 검증 결과. 문제가 없으면 '저장'과 '백테스트'로 이어집니다.
봉 단위(지금은 일봉)와 검증 결과. 문제가 없으면 '저장'과 '백테스트'로 이어집니다.

백테스트 화면

저장한 전략은 백테스트 화면에서 기간·수수료·슬리피지·체결 시점·검증 구간 비율과 파라미터를 바꿔 다시 계산할 수 있습니다.

백테스트 결과 화면: 핵심 지표와 BTC 보유 비교, 자산 곡선, 검증 구간(오른쪽 음영). 아래로 민감도 표와 약한 구간이 이어집니다.
백테스트 결과 화면: 핵심 지표와 BTC 보유 비교, 자산 곡선, 검증 구간(오른쪽 음영). 아래로 민감도 표와 약한 구간이 이어집니다.

코드 전략 (Python)

코드 탭에서는 파이썬 파일 하나로 전략을 씁니다. 노코드 규칙과 같은 백테스트 엔진, 같은 가상거래·실거래 실행 경로를 쓰므로 같은 규칙이면 결과도 같습니다(아래 '예제'의 비교 표).

형식

파일에는 두 가지가 있어야 합니다. META 는 이름·시장·종목·파라미터·손절 옵션을 담은 값만의 dict 이고, decide(ctx) 는 봉이 마감될 때마다 한 번 불려 목표 비중을 돌려주는 함수입니다.

META = {
    "name": "내 첫 코드 전략",
    "market": "crypto_krw",
    "venues": ["upbit"],
    "symbols": ["BTC"],
    "params": {"n": {"type": "int", "default": 50, "min": 10, "max": 200}},
    "risk": {"stop_loss_pct": 10},
}


def decide(ctx):
    ma = ctx.sma("BTC", ctx.params["n"])
    if ma is None:
        return None            # 봉이 모자라면 지금 상태 유지
    if ctx.close("BTC")[-1] > ma:
        return {"BTC": 1.0}    # 전략 자금 100%
    return {}                  # 모두 매도(현금)
  • {종목: 비중}: 비중은 0~1, 합은 1 이하. 빠진 종목은 0(매도)입니다. 종목은 "BTC"처럼 써도 되고 "KRW-BTC" 같은 엔진 키로 써도 됩니다.
  • {}: 모두 팔고 현금.
  • None: 지금 들고 있는 그대로 유지(주문 없음). 교차처럼 '신호가 난 봉'에만 dict 를 돌려주고 나머지는 None 으로 두는 것이 기본 모양입니다.
  • 공매도와 차입은 없습니다. 음수 비중이나 합 1 초과는 오류로 그 전략을 멈춥니다.

편집기와 저장 전 검증

편집기는 문법 강조와 행 번호를 보여 주고, 입력하는 동안 문법 오류, 허용되지 않은 import, decide(ctx) 시그니처, META 값을 검사해 행 번호와 함께 알려 줍니다. 템플릿 3개(이동평균 교차·상대모멘텀 로테이션·RSI 평균회귀)는 학습용이며 성과를 보장하지 않습니다.

코드 탭: 템플릿 선택, 문법 강조 편집기, 사용할 수 있는 import 와 실행 한도.
코드 탭: 템플릿 선택, 문법 강조 편집기, 사용할 수 있는 import 와 실행 한도.
저장 전 검증: 허용되지 않은 import(1행)와 decide 의 인자 개수(24행)를 행 번호와 함께 표시합니다.
저장 전 검증: 허용되지 않은 import(1행)와 decide 의 인자 개수(24행)를 행 번호와 함께 표시합니다.
같은 화면에서 백테스트: 핵심 지표와 자산 곡선(보라: 전략, 회색: BTC 보유), 전체 결과 화면 링크.
같은 화면에서 백테스트: 핵심 지표와 자산 곡선(보라: 전략, 회색: BTC 보유), 전체 결과 화면 링크.

ctx API 레퍼런스

아래 표는 키트 코드(rules/code_api.py)의 docstring 에서 그대로 만든 것입니다.

필드타입설명
ctx.symbolstuple[str]이 전략이 거래하는 종목(엔진 키: KRW-BTC, BTCUSDT, 005930). META 의 symbols 순서.
ctx.nowstr판단 봉(마지막으로 마감된 봉)의 날짜 "YYYY-MM-DD". 주문은 다음 봉 시가에 체결됩니다.
ctx.paramsdict이번 실행의 파라미터 값. META["params"] 의 기본값, 또는 백테스트 화면에서 바꾼 값.
ctx.positionsdict[str, float]지금 들고 있는 종목과 목표 비중 {종목: 0~1}. 전략의 지난 결정과 손절·익절이 남긴 것입니다. 체결 뒤 가격이 움직이므로 계좌의 실제 비중과는 조금 다를 수 있습니다.
ctx.cashfloat1 - sum(positions): 전략 자금 중 포지션에 들어가지 않은 몫.
ctx.statedict다음 판단까지 이어지는 dict. JSON 값(숫자·문자열·리스트·dict)만, 64KB 까지. 처음에는 비어 있습니다.
함수설명
ctx.bars(symbol: 'str') -> Bars판단 봉까지 마감된 봉(최대 300개, Bars). 아직 시세가 없는 종목이면 KeyError.
ctx.close(symbol: 'str') -> tuple종가 튜플(오래된 것부터). ctx.bars(symbol).close 와 같습니다.
ctx.has(symbol: 'str') -> bool판단 봉 날짜에 이 종목의 봉이 있으면 True(상장돼 있고 그날 거래됨).
ctx.weight(symbol: 'str') -> float지금 들고 있는 이 종목의 목표 비중(0~1, 안 들고 있으면 0). ctx.positions 를 종목 이름으로 찾는 것과 같습니다.
ctx.entry_price(symbol: 'str') -> float | None들고 있는 포지션의 매수가(매수 체결 봉의 시가). 들고 있지 않으면 None.
ctx.bars_held(symbol: 'str') -> int | None매수를 결정한 봉 뒤로 지난 봉 수. 들고 있지 않으면 None.
ctx.log(*parts) -> None이번 판단에 대한 메모. 판단 이유와 함께 기록됩니다(판단당 20줄, 줄당 300자까지).

Bars: 한 종목의 마감된 봉(오래된 것부터, 마지막이 판단 봉). 읽기 전용입니다. `date`·`open`·`high`·`low`·`close`·`volume` 은 길이가 같은 튜플이고, `len(bars)` 는 봉 개수(최대 300), `bars[-1]`·`bars.last` 는 마지막 봉(dict)입니다.

지표 함수

모든 지표 함수는 마지막 값(float) 하나를 돌려주고, 봉이 모자라 아직 값이 없으면 None 입니다. ago=1 은 직전 봉의 값입니다(교차 판단용). 지표는 필요한 봉 수의 3배(최소 60, 최대 250) 창으로 계산해 노코드 빌더와 같은 값이 나옵니다.

함수지표인자 범위반환
ctx.sma(symbol: 'str', n: 'int' = 20, ago: 'int' = 0) -> float | None단순이동평균 (SMA)n 2~200단순이동평균: 최근 n개 종가의 평균.
ctx.ema(symbol: 'str', n: 'int' = 20, ago: 'int' = 0) -> float | None지수이동평균 (EMA)n 2~200지수이동평균: alpha = 2/(n+1), 첫 값은 처음 n개 종가의 평균.
ctx.rsi(symbol: 'str', n: 'int' = 14, ago: 'int' = 0) -> float | NoneRSIn 2~100Wilder RSI(0~100).
ctx.macd(symbol: 'str', fast: 'int' = 12, slow: 'int' = 26, signal: 'int' = 9, field: 'str' = 'line', ago: 'int' = 0) -> float | NoneMACDfast 2~100, slow 3~200, signal 2~100MACD. field: "line"(EMA fast - EMA slow), "signal"(line 의 EMA), "hist"(line - signal).
ctx.bb(symbol: 'str', n: 'int' = 20, k: 'float' = 2.0, field: 'str' = 'middle', ago: 'int' = 0) -> float | None볼린저 밴드n 2~200, k 0.5~5.0볼린저 밴드. field: "upper", "middle"(SMA n), "lower"(middle ± k × 모표준편차), "pctb"((종가-하단)/(상단-하단)).
ctx.adx(symbol: 'str', n: 'int' = 14, field: 'str' = 'adx', ago: 'int' = 0) -> float | NoneADX (추세 강도)n 2~100Wilder ADX(추세 강도 0~100). field: "adx", "plus_di", "minus_di".
ctx.atr(symbol: 'str', n: 'int' = 14, ago: 'int' = 0) -> float | NoneATR (평균 진폭)n 2~100Wilder ATR(평균 진폭, 가격 단위).
ctx.stoch(symbol: 'str', k: 'int' = 14, d: 'int' = 3, field: 'str' = 'k', ago: 'int' = 0) -> float | None스토캐스틱k 2~100, d 1~50스토캐스틱. field: "k"(%K, k봉), "d"(%K 의 d봉 단순평균), 0~100.
ctx.volume_sma(symbol: 'str', n: 'int' = 20, ago: 'int' = 0) -> float | None거래량 이동평균n 2~200최근 n봉 거래량 평균.
ctx.high_n(symbol: 'str', n: 'int' = 20, ago: 'int' = 0) -> float | NoneN봉 신고가n 2~200이번 봉을 뺀 직전 n봉의 최고가. 종가 > high_n 이면 n봉 신고가 돌파.
ctx.low_n(symbol: 'str', n: 'int' = 20, ago: 'int' = 0) -> float | NoneN봉 신저가n 2~200이번 봉을 뺀 직전 n봉의 최저가.
ctx.rvol(symbol: 'str', n: 'int' = 20, ago: 'int' = 0) -> float | None실현 변동성 (연율 %)n 2~200실현 변동성(%): 최근 n개 로그수익률의 표본표준편차를 연율화(코인 365, 주식 252).

META 키

키타입필수설명
namestr필수전략 이름(60자까지).
descriptionstr선택설명(600자까지).
marketstr필수crypto_krw(국내 코인, 원화) · crypto_usdt(해외 코인, USDT) · kr_stock(국내 주식).
venueslist[str]선택실행할 거래소·증권사. 없으면 시장의 첫 번째(upbit · binance · kis).
symbolslist[str]필수거래할 종목 1~30개. 코인은 BTC·ETH, 국내 주식은 6자리 코드(005930).
timeframestr선택봉 단위. 지금은 "1d"(일봉)만 됩니다.
paramsdict선택파라미터 스키마(8개까지). 백테스트 화면에서 값을 바꾸고 민감도 표를 그립니다.
riskdict선택손절·익절·트레일링·최대 보유. 엔진이 처리합니다(아래 표).
costsdict선택{"fee_pct", "slippage_pct"} 편도 %. 없으면 시장 기본값.
warmup_barsint선택백테스트에서 첫 판단 전에 쌓을 봉 수(1~250, 기본 200).

파라미터 스키마

META["params"] 의 각 항목은 아래 키를 가집니다. 백테스트 화면이 이 스키마로 입력 칸을 만들고, 민감도 표에서 기본값 주변(step 단위 ±2)을 다시 계산합니다.

키타입필수설명
typestr선택"int" 또는 "float"(기본 float).
defaultnumber필수기본값(min~max 안).
minnumber필수최솟값.
maxnumber필수최댓값.
stepnumber선택민감도 표에서 바꿔 보는 간격.
labelstr선택화면에 보이는 이름(40자까지).

손절·익절 옵션

META["risk"] 는 선언만 하면 엔진이 처리합니다. 노코드 빌더의 손절·익절과 같은 코드 경로를 씁니다. 모두 종가로 판단하고 다음 봉 시가에 팝니다. 손절로 판 종목은 그 봉에 다시 사지 않습니다.

키범위설명
stop_loss_pct0.1 ~ 90.0 %매수가(체결 봉 시가) 대비 이만큼(%) 내린 종가에서 다음 봉에 매도.
take_profit_pct0.1 ~ 1000.0 %매수가 대비 이만큼(%) 오른 종가에서 다음 봉에 매도.
trailing_pct0.1 ~ 90.0 %매수 뒤 가장 높았던 종가 대비 이만큼(%) 내린 종가에서 다음 봉에 매도.
max_hold_bars1 ~ 200봉매수를 결정한 봉 뒤 이만큼 봉이 지나면 다음 봉에 매도.

시장

<code>market</code>시장venues백테스트 시세기본 수수료·슬리피지
crypto_krw국내 코인 거래소 (원화)upbit, bithumb, coinone, gopax업비트 원화 일봉0.05% / 0.05%
crypto_usdt해외 코인 거래소 (USDT)binance, bybit, bitget, okx, gate바이낸스 USDT 일봉0.1% / 0.05%
kr_stock국내 주식 (한국투자증권·키움·토스)kis, kiwoom, toss증권사 API 일봉(내 한투·키움 키로 받음)0.015% / 0.1%

import 와 실행 한도

import용도
math표준 수학 함수
statistics표준 통계 함수
numpy배열 계산
pandas표 계산
avalonquant_runtime.backtest.indicatorsAvalonQuant 지표 모듈(빌더·백테스트와 같은 계산)

한도: 한 번의 판단 5초(시작 30초), 메모리 512MB, ctx.state 64KB, 코드 100KB, 종목 30개·파라미터 8개까지, 판단마다 봉 300개.

쓸 수 없는 이름: __builtins__, __import__, breakpoint, compile, delattr, eval, exec, exit, getattr, globals, help, input, locals, memoryview, open, quit, setattr, vars, 그리고 밑줄(_)로 시작하는 속성.

의뢰 전략과 불러오기·내보내기

코드 전략은 .py 파일로 내보내고 불러옵니다. AvalonQuant 가 개발 의뢰로 납품하는 전략도 같은 형식이며, 파일 마지막 줄의 서명(# aq-signature:)으로 'AvalonQuant 제작(의뢰 전략)'으로 표시됩니다. 서명이 없거나 내용이 바뀐 파일은 '사용자 파일'로 표시되고, 경고를 확인하기 전까지 실행하지 않습니다. 의뢰 전략을 고쳐 저장하면 내 전략이 되고, 내보낼 때 서명이 빠집니다.

백테스트 해석

연평균 복리수익률 (CAGR)

시작 자산이 끝 자산이 되려면 매년 몇 %씩 복리로 불어나야 했는지입니다. 기간이 짧으면 한두 번의 큰 움직임에 크게 흔들립니다.

최대 낙폭 (MDD)

자산이 직전 최고점에서 가장 많이 떨어졌던 비율입니다. 실제로 그 하락을 견디고 전략을 계속 돌릴 수 있는지 먼저 따져 보세요. 결과 화면의 '약한 구간'은 가장 깊은 하락의 고점·저점·회복일을 보여 줍니다.

샤프지수

일별 수익률의 평균을 표준편차로 나눠 연율화한 값입니다(코인 365일, 주식 252일, 무위험 수익률 0). 수익을 얻으려고 감수한 변동이 얼마나 컸는지를 봅니다.

민감도

파라미터를 기본값 주변으로 하나씩 바꿔 다시 계산한 표입니다. 한 값에서만 성과가 좋고 옆 값에서 무너지면 과최적화일 가능성이 큽니다. 결과 화면은 주변 값의 샤프가 기본값의 절반 이상인지로 '유지'를 표시합니다.

학습·검증 구간 분리

기본으로 마지막 30% 기간을 검증 구간(OOS)으로 떼어 따로 계산합니다. 파라미터는 학습 구간만 보고 정하고, 검증 구간 성과가 학습 구간의 절반에도 못 미치면 결과 화면이 경고합니다. 아래는 이동평균 교차 템플릿을 실제 업비트 일봉으로 돌린 결과입니다. 같은 전략이라도 구간에 따라 성과가 크게 달라집니다.

학습 구간 (2018-12-31 ~ 2024-06-03)검증 구간 (2024-06-03 ~ 2026-09-30)BTC 보유(전체 기간) (2018-12-31 ~ 2026-09-30)
연평균 복리수익률+91.21%+2.91%+53.08%
최대 낙폭-40.48%-35.19%-74.13%
샤프지수1.720.241.06

일봉 체결 가정과 한계

  • 신호는 봉 마감(종가) 기준, 체결은 다음 봉 시가입니다. 같은 봉 종가에 체결하는 옵션은 없습니다(미래 참조 방지).
  • 손절·익절·트레일링도 종가로 판단합니다. 장중에 그 가격을 지나쳐도 다음 봉 시가에 팔리므로, 갭이 크면 손절 폭보다 더 잃을 수 있습니다.
  • 수수료와 슬리피지는 매 거래 편도로 뺍니다(국내 코인 기본 0.05% + 0.05%). 실제 체결 가격·호가 단위·최소 주문 금액은 백테스트와 다를 수 있습니다.
  • 지금은 일봉만 저장·실행할 수 있습니다. 분·시간봉은 분봉 엔진이 들어온 뒤 열립니다.

예제 3개: 전체 코드와 실행 결과

키트에 들어 있는 템플릿 그대로입니다. 결과 수치는 키트의 백테스트(같은 엔진·같은 샌드박스)를 업비트 원화 일봉으로 실제로 돌린 값이고, 키트 테스트가 이 문서의 코드와 수치를 다시 실행해 확인합니다. 모두 학습용이며 성과를 보장하지 않습니다.

이동평균 교차

"""이동평균 교차 -- 학습용 템플릿입니다. 코드 전략을 만드는 방법을 보여 줄 뿐이며 성과를 보장하지 않습니다.

빠른 단순이동평균이 느린 단순이동평균을 위로 뚫고 올라가면(골든 크로스) 사고, 아래로 뚫고 내려가면 팝니다.
노코드 빌더의 '이동평균 교차' 예시와 같은 규칙이라 백테스트 결과도 같습니다.
"""

META = {
    "name": "템플릿: 이동평균 교차",
    "description": "SMA(빠름)이 SMA(느림)을 위로 뚫으면 사고 아래로 뚫으면 팝니다. 학습용 템플릿, 성과 보장 아님.",
    "market": "crypto_krw",
    "venues": ["upbit"],
    "symbols": ["BTC"],
    "timeframe": "1d",
    "params": {
        "fast": {"type": "int", "default": 20, "min": 2, "max": 100, "step": 2, "label": "빠른 이동평균(봉)"},
        "slow": {"type": "int", "default": 60, "min": 10, "max": 200, "step": 6, "label": "느린 이동평균(봉)"},
    },
    "risk": {"stop_loss_pct": None, "take_profit_pct": None, "trailing_pct": None, "max_hold_bars": None},
    "warmup_bars": 183,
}


def decide(ctx):
    fast, slow = ctx.params["fast"], ctx.params["slow"]
    f_now, f_prev = ctx.sma("BTC", fast), ctx.sma("BTC", fast, ago=1)
    s_now, s_prev = ctx.sma("BTC", slow), ctx.sma("BTC", slow, ago=1)
    if f_now is None or f_prev is None or s_now is None or s_prev is None:
        return None  # 아직 봉이 모자람: 지금 상태 유지
    if f_prev <= s_prev and f_now > s_now:
        ctx.log(f"골든 크로스 SMA{fast}={f_now:.0f} > SMA{slow}={s_now:.0f}")
        return {"BTC": 1.0}  # 전략 자금 100% 매수
    if f_prev >= s_prev and f_now < s_now:
        ctx.log(f"데드 크로스 SMA{fast}={f_now:.0f} < SMA{slow}={s_now:.0f}")
        return {}  # 모두 매도(현금)
    return None  # 교차가 없으면 그대로

업비트 원화 일봉 2018-12-31 ~ 2026-09-30, 거래마다 수수료 0.05% + 슬리피지 0.05%, 다음 봉 시가 체결, 기본 파라미터.

지표결과
연평균 복리수익률+58.78%
최대 낙폭-40.48%
샤프지수1.39
총수익률+3496.18%
거래 횟수49
승률45.8%

민감도: 빠른 이동평균(봉)

값연복리최대 낙폭샤프
16+59.03%-43.13%1.39
18+60.52%-45.59%1.41
20 (기본)+58.78%-40.48%1.39
22+48.58%-60.67%1.18
24+48.47%-58.42%1.17

민감도: 느린 이동평균(봉)

값연복리최대 낙폭샤프
48+56.47%-46.56%1.33
54+60.06%-46.06%1.40
60 (기본)+58.78%-40.48%1.39
66+47.40%-61.67%1.16
72+52.43%-51.09%1.23

학습용 템플릿입니다. 코드 전략을 만드는 방법을 보여 줄 뿐이며 성과를 보장하지 않습니다.

상대모멘텀 로테이션

"""상대모멘텀 로테이션 -- 학습용 템플릿입니다. 코드 전략을 만드는 방법을 보여 줄 뿐이며 성과를 보장하지 않습니다.

매달 첫 봉에 후보 종목의 최근 N봉 수익률을 비교해 가장 많이 오른 한 종목에 전략 자금을 모두 넣습니다. 모두
내렸으면(수익률 0 이하) 현금으로 둡니다. 달이 바뀌었는지는 ctx.state 에 적어 두고 확인합니다.
"""

META = {
    "name": "템플릿: 상대모멘텀 로테이션",
    "description": "매달 첫 봉에 최근 N봉 수익률이 가장 높은 코인 하나를 들고, 모두 마이너스면 현금. 학습용 템플릿, 성과 보장 아님.",
    "market": "crypto_krw",
    "venues": ["upbit"],
    "symbols": ["BTC", "ETH", "XRP"],
    "timeframe": "1d",
    "params": {
        "lookback": {"type": "int", "default": 90, "min": 10, "max": 250, "step": 10, "label": "수익률 비교 기간(봉)"},
    },
    "risk": {"stop_loss_pct": None, "take_profit_pct": None, "trailing_pct": None, "max_hold_bars": None},
    "warmup_bars": 120,
}


def decide(ctx):
    month = ctx.now[:7]
    if ctx.state.get("month") == month:
        return None  # 이번 달은 이미 골랐음: 그대로
    ctx.state["month"] = month
    n = ctx.params["lookback"]
    best, best_return = None, 0.0
    for symbol in ctx.symbols:
        if not ctx.has(symbol):
            continue
        close = ctx.close(symbol)
        if len(close) <= n:
            continue
        r = close[-1] / close[-1 - n] - 1
        if r > best_return:
            best, best_return = symbol, r
    if best is None:
        ctx.log(f"{n}봉 수익률이 모두 0 이하 -- 현금")
        return {}
    ctx.log(f"{best} {n}봉 수익률 {best_return * 100:.1f}% -- 가장 높음")
    return {best: 1.0}

업비트 원화 일봉 2018-12-31 ~ 2026-09-30, 거래마다 수수료 0.05% + 슬리피지 0.05%, 다음 봉 시가 체결, 기본 파라미터.

지표결과
연평균 복리수익률-0.09%
최대 낙폭-92.27%
샤프지수0.34
총수익률-0.69%
거래 횟수63
승률48.4%

민감도: 수익률 비교 기간(봉)

값연복리최대 낙폭샤프
70+1.99%-88.31%0.38
80+15.84%-90.59%0.56
90 (기본)-0.09%-92.27%0.34
100+4.51%-85.34%0.41
110+11.45%-68.36%0.50

학습용 템플릿입니다. 코드 전략을 만드는 방법을 보여 줄 뿐이며 성과를 보장하지 않습니다.

RSI 평균회귀

"""RSI 평균회귀 -- 학습용 템플릿입니다. 코드 전략을 만드는 방법을 보여 줄 뿐이며 성과를 보장하지 않습니다.

RSI 가 과매도선(30)을 아래에서 위로 넘으면 사고, 과매수선(70)을 넘으면 팝니다. 손절 8%·최대 보유 20봉은 META 의
risk 로 선언해 엔진이 처리합니다. 노코드 빌더의 'RSI 과매도 반등' 예시와 같은 규칙이라 백테스트 결과도 같습니다.
"""

META = {
    "name": "템플릿: RSI 평균회귀",
    "description": "RSI 가 과매도선을 위로 넘으면 사고 과매수선을 넘거나 8% 손실·20봉 보유면 팝니다. 학습용 템플릿, 성과 보장 아님.",
    "market": "crypto_krw",
    "venues": ["upbit"],
    "symbols": ["BTC"],
    "timeframe": "1d",
    "params": {
        "period": {"type": "int", "default": 14, "min": 2, "max": 50, "step": 1, "label": "RSI 기간(봉)"},
        "low": {"type": "float", "default": 30, "min": 5, "max": 50, "step": 2.5, "label": "과매도선"},
        "high": {"type": "float", "default": 70, "min": 50, "max": 95, "step": 2.5, "label": "과매수선"},
    },
    "risk": {"stop_loss_pct": 8, "take_profit_pct": None, "trailing_pct": None, "max_hold_bars": 20},
    "warmup_bars": 60,
}


def decide(ctx):
    p = ctx.params
    now, prev = ctx.rsi("BTC", p["period"]), ctx.rsi("BTC", p["period"], ago=1)
    if now is None or prev is None:
        return None
    if ctx.weight("BTC") > 0:  # 들고 있을 때: 과매수면 매도
        if now > p["high"]:
            ctx.log(f"RSI {now:.1f} > {p['high']} -- 매도")
            return {}
        return None
    if prev <= p["low"] < now:  # 안 들고 있을 때: 과매도에서 벗어나면 매수
        ctx.log(f"RSI {prev:.1f} -> {now:.1f}, {p['low']} 상향 돌파 -- 매수")
        return {"BTC": 1.0}
    return None

업비트 원화 일봉 2018-12-31 ~ 2026-09-30, 거래마다 수수료 0.05% + 슬리피지 0.05%, 다음 봉 시가 체결, 기본 파라미터.

지표결과
연평균 복리수익률-7.61%
최대 낙폭-60.47%
샤프지수-0.28
총수익률-45.85%
거래 횟수60
승률33.3%

민감도: RSI 기간(봉)

값연복리최대 낙폭샤프
12-8.93%-61.46%-0.32
13-6.78%-57.75%-0.21
14 (기본)-7.61%-60.47%-0.28
15-10.47%-66.47%-0.45
16-14.93%-74.42%-0.74

민감도: 과매도선

값연복리최대 낙폭샤프
25.0-12.77%-67.25%-0.66
27.5-10.83%-67.59%-0.50
30.0 (기본)-7.61%-60.47%-0.28
32.5-5.97%-57.98%-0.18
35.0-0.86%-46.48%0.08

민감도: 과매수선

값연복리최대 낙폭샤프
65.0-9.60%-65.88%-0.40
67.5-8.60%-62.85%-0.34
70.0 (기본)-7.61%-60.47%-0.28
72.5-7.61%-60.47%-0.28
75.0-7.61%-60.47%-0.28

학습용 템플릿입니다. 코드 전략을 만드는 방법을 보여 줄 뿐이며 성과를 보장하지 않습니다.

노코드와 코드, 같은 결과

같은 전략을 노코드 규칙과 코드로 각각 만들어 같은 시세로 돌린 결과입니다. 자산 곡선까지 봉마다 같습니다.

전략연복리최대 낙폭샤프거래
노코드: 이동평균 교차+58.78%-40.48%1.3949
코드: ma_cross+58.78%-40.48%1.3949
노코드: RSI 과매도 반등-7.61%-60.47%-0.2860
코드: rsi_reversion-7.61%-60.47%-0.2860

자주 하는 실수

미래 참조

오늘 종가로 판단하고 오늘 종가에 체결했다고 가정하면 실제로는 불가능한 성과가 나옵니다. 키트는 판단 봉까지의 마감된 봉만 ctx 에 넣고 체결은 다음 봉 시가로 하므로, 코드에서 미래 봉에 접근할 방법이 없습니다. 다만 '다음 달에 상장폐지될 종목을 미리 뺀다' 같은 판단을 코드에 직접 써 넣으면 그것도 미래 참조입니다.

과최적화

파라미터를 수십 번 바꿔 가장 좋은 값을 고르면 과거에만 맞는 전략이 됩니다. 학습 구간에서만 파라미터를 고르고, 검증 구간과 민감도 표로 확인하세요. 조건을 계속 덧붙이는 것도 같은 문제입니다.

생존 편향

지금 거래되는 종목만으로 과거를 돌리면 그사이 사라진 종목이 빠져 성과가 좋게 나옵니다. 거래대금 상위 N 처럼 후보 목록을 쓰는 전략은 특히 조심하세요.

수수료·슬리피지 누락

매매가 잦은 전략은 비용에 민감합니다. 수수료와 슬리피지를 0 으로 두고 좋아 보이는 전략은 대부분 실제로는 다릅니다. 결과 화면의 총 수수료·슬리피지와 거래 횟수를 함께 보세요.

모듈 전역 변수에 상태 쌓기

코드 전략에서 봉 사이에 이어져야 하는 값은 ctx.state 에 두세요. 모듈 전역 변수는 실행할 때마다 새 프로세스에서 다시 시작하므로 백테스트와 실행 결과가 달라집니다.

안전·보안

API 키는 내 PC 에만

거래소·증권사 API 키는 내 PC 의 키트에만 암호화해 저장하고 AvalonQuant 서버로 보내지 않습니다. 출금 권한은 필요 없습니다. 키를 만들 때 출금 권한을 끄고, 거래소가 지원하면 허용 IP 를 등록하세요.

가상거래와 시세 조회용 키

가상거래를 시작하려면 라이브 API 키를 시세 조회 전용으로 넣어야 합니다. 가상거래에서는 주문이 나가지 않으며, 키트가 그 키로 받은 실제 시세로 체결을 계산합니다. 실거래를 켤 때만 주문 권한이 있는 키를 씁니다.

코드 전략 샌드박스

코드 전략은 내 PC 안의 별도 프로세스에서 실행됩니다. 빈 환경변수와 빈 임시 폴더에서 시작하고, 파일 쓰기·키트 폴더 읽기·네트워크·다른 프로그램 실행·환경변수 읽기가 막혀 있어 전략 코드가 API 키에 닿을 수 없습니다. 한 번의 판단에 시간·메모리 한도가 있어 무한 루프나 메모리 폭주는 그 전략만 멈춥니다.

오류가 나면

전략 코드에서 예외가 나면 그 전략만 멈추고, 설정한 알림 채널(텔레그램·웹훅)로 몇 번째 줄에서 무슨 오류인지 보냅니다. 다른 전략은 그대로 돌아갑니다. 코드를 고쳐 저장하면 다시 실행됩니다.

남이 준 전략 파일

서명 없는 전략 파일은 '사용자 파일'로 표시되고 경고를 확인하기 전까지 실행하지 않습니다. 코드와 백테스트를 직접 확인하고, 실거래 전에는 가상거래로 먼저 돌려 보세요.

자주 묻는 질문

코딩을 몰라도 전략을 만들 수 있나요?

네. 노코드 탭에서 지표·비교·값 블록을 조립하면 됩니다. 노코드로 표현하기 어려운 로테이션이나 상태가 필요한 규칙은 코드 탭이 맞습니다.

코드 전략에서 외부 라이브러리나 인터넷 데이터를 쓸 수 있나요?

아니요. import 는 math·statistics·numpy·pandas 와 AvalonQuant 지표 모듈만 되고 네트워크는 막혀 있습니다. 시세는 ctx 로 받습니다.

분봉·시간봉 전략도 되나요?

지금은 일봉만 저장·실행할 수 있습니다. 분·시간봉은 분봉 엔진이 들어온 뒤 열립니다.

무료로 몇 개까지 돌릴 수 있나요?

만들기와 백테스트는 제한이 없고, 실행은 가상거래 1개까지입니다. 유료 이용권은 등급의 슬롯 수만큼 실행합니다.

가상거래에도 API 키가 필요한가요?

네. 실제 시세를 받아야 하므로 라이브 API 키를 시세 조회 전용으로 넣어야 시작할 수 있습니다. 주문은 나가지 않습니다.

백테스트 결과가 좋으면 실거래도 그만큼 나오나요?

보장되지 않습니다. 과거 데이터 검증일 뿐이며 체결 가격·비용·시장 변화로 달라집니다. 검증 구간과 민감도를 확인하고 가상거래로 먼저 확인하세요.

전략 개발을 맡길 수 있나요?

네. 개발 요청으로 규칙을 알려 주시면 이 형식(.py 또는 규칙 파일)으로 서명해 납품합니다. 범위와 결제는 따로 확인합니다.

고지

이 문서와 예제는 교육용입니다. 투자 권유가 아니며, 어떤 성과도 보장하지 않습니다. 백테스트는 과거 데이터 검증일 뿐 미래 수익을 보장하지 않습니다. 실거래의 손익과 판단은 사용자 책임이며, 실거래는 작은 금액으로 시작하세요.