<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>myaistories 님의 블로그</title>
    <link>https://myaistories.tistory.com/</link>
    <description>myaistories 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Fri, 17 Jul 2026 14:08:02 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>myaistories</managingEditor>
    <item>
      <title>[시즌 3 - 최종화] &amp;quot;24시간 돌아가는 나의 분신&amp;quot; 클라우드 자동매매 시스템 최종 배포와 무한 고도화 로드맵</title>
      <link>https://myaistories.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 지난 시즌 1(파이썬 매매 봇 기초 조립)과 시즌 2(3개년 과거 백테스팅 검증)의 성과에 이어, 이번 시즌 3에서는 로컬 PC를 벗어나 전 세계에서 가장 안정적인 가상 서버 위에서 24시간 스스로 판단하고, 기억을 저장하며, 위기 상황에 자가 치유하고 스마트폰으로 직접 통제할 수 있는 **'클라우드 무인 자동매매 인프라'**를 마침내 완벽히 직조해 냈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 대단원의 마침표를 찍으며, 우리가 구축한 시스템의 전체 그림을 정리하고, 실물 투자 가동 직전 반드시 점검해야 할 &lt;b&gt;'최종 출시 10대 체크리스트'&lt;/b&gt;, 그리고 한 발자국 더 나아가 데이터 사이언스와 AI 머신러닝을 접목해 봇을 인공지능 트레이더로 업그레이드할 **'무한 고도화 로드맵'**을 제시하겠습니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 시즌 3 통합 아키텍처 다이어그램&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 조립한 시스템은 단순한 스크립트 쪼가리가 아닙니다. 서버 인프라, 영구 저장소, 대외 통신 채널, 자가 생존 장치들이 긴밀히 결합한 유기적인 소프트웨어 시스템입니다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;       [ 리눅스 클라우드 서버 (AWS LightSail Ubuntu 22.04) ]
┌──────────────────────────────────────────────────────────────┐
│                                                              │
│     ┌──────────────────┐               ┌────────────────┐    │
│     │   감시자 (Cron)  │ ──(1분 주기)─&amp;gt; │ 쉘 스크립트    │    │
│     └──────────────────┘               │ (check_bot.sh) │    │
│                                        └───────┬────────┘    │
│                                                │ (프로세스 다운 시 재부팅)
│                                                ▼             │
│  ┌────────────────────────────────────────────────────────┐  │
│  │   자동매매 코어 엔진 (Python / main.py)                 │  │
│  │                                                        │  │
│  │  ┌──────────────┐   ┌──────────────┐   ┌─────────────┐  │  │
│  │  │ 전략 연산   │&amp;lt;─&amp;gt;│ 텔레그램     │&amp;lt;─&amp;gt;│  지수       │  │  │
│  │  │ (Kelly, ATR) │   │ 제어 스레드  │   │  백오프     │  │  │
│  │  └──────┬───────┘   └──────┬───────┘   └──────┬──────┘  │  │
└──┼─────────┼──────────────────┼──────────────────┼────────┼──┘
             │                  │ (명령 송수신)    │ (시세/주문 API)
             ▼                  ▼                  ▼
      ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
      │ SQLite DB    │   │ 텔레그램 앱  │   │ 가상자산     │
      │ (trade_bot)  │   │ (마스터폰)   │   │ 거래소 (API) │
      └──────────────┘   └──────────────┘   └──────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 실전 투입(Go-Live) 직전 필수 10대 체크리스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 돈을 투입하여 시스템을 활성화하기 직전, 단 하나의 실수도 없도록 아래 체크리스트를 인쇄하여 수동으로 하나하나 확인해야 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;[ ] 샌드박스 모드 비활성화&lt;/b&gt;: CCXT 거래소 인스턴스 설정 시 &lt;code&gt;exchange.set_sandbox_mode(False)&lt;/code&gt;로 올바르게 Live Mode로 전환되었는가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[ ] 실물 API 권한 최소화&lt;/b&gt;: 거래소 사이트에서 API 발급 시 출금(Withdrawal) 권한은 철저히 체크 해제하고, 오직 조회(Read) 및 주문(Trade) 권한만 부여했는가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[ ] 서버 시간대 동기화&lt;/b&gt;: &lt;code&gt;timedatectl&lt;/code&gt;을 통해 AWS 서버 시간이 &lt;code&gt;Asia/Seoul&lt;/code&gt; 또는 UTC 기준 거래소 서버 시간과 완벽히 오차 범위 내로 동기화되었는가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[ ] 텔레그램 보안 필터링&lt;/b&gt;: &lt;code&gt;AUTHORIZED_CHAT_ID&lt;/code&gt;를 엄격히 지정하여 본인 이외의 제3자가 전송하는 제어 명령어가 먹히지 않도록 조치했는가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[ ] 분할 켈리 배수 탑재&lt;/b&gt;: 욕심에 눈이 멀어 켈리 값을 100% 반영하지 않고, 쿼터 켈리(0.25)나 하프 켈리(0.50)를 곱해 변동성을 억제해 두었는가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[ ] 최소 주문 단위 준수&lt;/b&gt;: 비트코인, 이더리움 등 대상 코인들의 소수점 단위와 최소 금액 기준(예: 업비트는 원화 5,000원 이상)이 매수/매도 로직에 정확히 반영되었는가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[ ] 지수 백오프 작동 테스트&lt;/b&gt;: 일시적 와이파이 단절이나 API 통신 오류 발생 시, 봇이 즉시 튕겨서 사망하지 않고 예외 구문으로 빠져 점진적 재시도를 시작하는가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[ ] 크론탭 예토전생 가동&lt;/b&gt;: 봇을 강제로 &lt;code&gt;kill -9&lt;/code&gt;로 종료시켰을 때, 1분 이내에 크론탭 감시 쉘이 감지하여 프로세스를 백그라운드로 안전하게 되살려내는가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[ ] DB 락(Lock) 해제 타임아웃&lt;/b&gt;: SQLite 설정 시 &lt;code&gt;sqlite3.connect('trade_bot.db', timeout=30.0)&lt;/code&gt;와 같이 동시 접속 잠금 방어 코드가 삽입되었는가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[ ] 여유 자금 베팅 원칙&lt;/b&gt;: 절대로 당장 사용해야 하는 생활비나 대출금으로 돌리지 않고, 시스템 오류로 인한 유실 가능성을 인지한 뒤 감당 가능한 투자 범위 내에서만 운용을 시작하는가?&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 미래 지향적 무한 고도화 로드맵: 머신러닝의 접목&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 구축한 이 견고한 프레임워크는 향후 더 똑똑한 인공지능 의사결정 모델을 갈아 끼우는 '뼈대'가 됩니다. 앞으로 시도해 볼 만한 대표적인 머신러닝 고도화 과제를 제시합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  머신러닝 예측 기반의 신호 필터링 (Signal Filter)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 이동평균선 크로스에 의존하는 대신, 머신러닝 분류기(Classification) 모델을 사용하여 진입 신호를 필터링할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 수집&lt;/b&gt;: 1시간 봉 단위로 거래소 과거 데이터(OHLCV) 및 보조지표(RSI, MACD, Bollinger Bands)를 데이터프레임으로 변환합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예측 대상 (Target)&lt;/b&gt;: 진입 후 12시간 이내에 +3% 상승(1)할지, 혹은 하락(0)할지 예측하는 모델을 학습시킵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모델 적용&lt;/b&gt;: 매수 신호가 발생했을 때, 학습된 머신러닝 모델(예: XGBoost 또는 LightGBM)에 실시간 데이터를 피딩하여 예측 확률이 70% 이상일 때만 진짜 진입을 집행하도록 최종 스크리닝 필터를 탑재합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# 머신러닝 모델을 활용한 진입 판독 표준 예시 코드
import joblib
import pandas as pd
import numpy as np

def prepare_features(ohlcv_df):
    &quot;&quot;&quot;
    실시간 봉 데이터로부터 머신러닝 입력용 특성(Features)을 계산합니다.
    &quot;&quot;&quot;
    features = {}
    
    # 1. 이동평균선(SMA) 비율 계산
    features['sma_ratio'] = ohlcv_df['close'].rolling(20).mean().iloc[-1] / ohlcv_df['close'].iloc[-1]
    
    # 2. RSI (상대강도지수) 계산
    delta = ohlcv_df['close'].diff()
    gain = (delta.where(delta &amp;gt; 0, 0)).rolling(14).mean().iloc[-1]
    loss = (-delta.where(delta &amp;lt; 0, 0)).rolling(14).mean().iloc[-1]
    rs = gain / (loss if loss &amp;gt; 0 else 0.00001)
    features['rsi'] = 100 - (100 / (1 + rs))
    
    # 3. 거래량 증가율 (최근 10봉 평균 대비 현재 봉)
    features['volume_ratio'] = ohlcv_df['volume'].iloc[-1] / ohlcv_df['volume'].rolling(10).mean().iloc[-1]
    
    return features

def check_ml_signal(ohlcv_df):
    &quot;&quot;&quot;
    학습된 XGBoost 모델을 로드하여, 실시간 지표 기반으로 
    이번 진입의 상승 성공 확률을 예측 판단합니다.
    &quot;&quot;&quot;
    try:
        # 1. 사전에 학습해 가상 서버에 저장해 둔 머신러닝 모델 로드
        model = joblib.load(&quot;trading_xgb_model.pkl&quot;)
        
        # 2. 실시간 특성 데이터 추출 및 DataFrame화 (컬럼 순서 정렬)
        features_dict = prepare_features(ohlcv_df)
        features_df = pd.DataFrame([features_dict])
        
        # 3. 매수 진입 시 상승 성공할 확률 예측 (Probability)
        prob = model.predict_proba(features_df)[0][1] # 클래스 1(상승)의 확률
        
        # 예측 확률이 72%를 초과할 때만 진입 승인
        if prob &amp;gt; 0.72:
            print(f&quot;[ML 판단] 진입 타당성 검증 통과 - 예상 상승 확률: {prob*100:.1f}%&quot;)
            return True
        else:
            print(f&quot;[ML 판단] 진입 거절 - 예상 상승 확률 미달: {prob*100:.1f}%&quot;)
            return False
    except Exception as e:
        print(f&quot;[ML 에러] 모델 추론 실패 (보수적으로 진입 차단): {e}&quot;)
        return False
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 대장정을 마무리하며: &quot;나의 봇은 오늘도 일합니다&quot;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동매매 시스템을 만든다는 것은 단순히 돈을 버는 기술을 코딩하는 것 이상의 의미가 있습니다. 그것은 **'감정에 휘둘리는 인간 본성과의 작별'**이자, 나만의 규칙과 논리를 24시간 동안 대신 수행해 주는 **'나의 분신(Agent)을 창조하는 여정'**입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거 백테스트 데이터의 역사적 흐름을 거쳐, 클라우드라는 광활한 네트워크 전장에서 안전장치를 메고 묵묵히 싸우는 여러분의 트레이더 봇은 오늘도 밤잠 설치지 않고 일할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 연재가 여러분의 트레이딩을 수학적으로 객관화하고, 시스템적으로 한 차원 끌어올리는 튼튼한 디딤돌이 되었기를 소망합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;My AI Stories 시즌 3 완결. 그동안 함께 이 인프라를 지어 올린 독자 여러분께 깊은 감사를 드립니다. 봇의 성공적인 구동을 응원하며, 질문과 피드백은 언제나 환영합니다!&lt;/b&gt;&lt;/p&gt;</description>
      <author>myaistories</author>
      <guid isPermaLink="true">https://myaistories.tistory.com/29</guid>
      <comments>https://myaistories.tistory.com/29#entry29comment</comments>
      <pubDate>Wed, 27 May 2026 06:55:56 +0900</pubDate>
    </item>
    <item>
      <title>[시즌 3 - 7화] &amp;quot;절대로 잠들지 않는 파수꾼&amp;quot; API 호출 제한(Rate Limit) 방어와 봇 자가 치유(Self-Healing) 프로세스 구축</title>
      <link>https://myaistories.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 지난 연재에서는 여러 자산군에 수학적으로 안전하게 시드를 나누고 분배하는 '정적 켈리 공식 자금 관리 모듈'을 이식하여 리스크 방어망을 견고히 구축했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 트레이딩 봇의 기능적 구현은 완성에 가까워졌지만, 운영 관점에서의 진짜 복병이 남아 있습니다. 바로 **'예외 상황으로 인한 프로세스 셧다운'**입니다. 24시간 작동하는 봇은 아무리 코딩을 완벽하게 했더라도 다음과 같은 현실적인 물리 세계의 에러를 무조건 겪게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;거래소 점검으로 인한 일시적인 API 서버 응답 없음 (Request Timeout)&lt;/li&gt;
&lt;li&gt;단시간에 너무 자주 가격을 조회하여 거래소로부터 받는 일시적 IP 차단 (HTTP 429 Too Many Requests)&lt;/li&gt;
&lt;li&gt;가상 서버(AWS 라이트세일) 내부의 일시적 네트워크 순단&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 예외가 발생했을 때 봇이 에러 로그를 뿜으며 그냥 죽어버린다면, 자는 동안 혹은 이동 중에 매매 기회를 모두 잃게 됩니다. 오늘 연재에서는 에러 앞에서도 지치지 않고 버티는 &lt;b&gt;'지수 백오프(Exponential Backoff)'&lt;/b&gt; 재시도 패턴과, 프로세스가 죽었을 때 스스로 심폐소생술을 수행하는 **'크론탭 자가치유(Self-Healing) 스크립트'**를 완성해 보겠습니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. API 호출 제한 방어: 지수 백오프(Exponential Backoff) 알고리즘&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거래소 API 서버는 무분별한 트래픽 공격을 막기 위해 1초당 요청 횟수를 엄격하게 제한합니다. 이를 초과하면 &lt;code&gt;HTTP 429&lt;/code&gt; 또는 &lt;code&gt;DDoS Ban&lt;/code&gt;을 날려 일정 시간 동안 봇을 무력화시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 에러가 났을 때 &lt;code&gt;while True&lt;/code&gt;로 즉시 재시도하면 차단 기간이 더 연장됩니다. 따라서 에러가 날 때마다 재시도 간격을 &lt;b&gt;2배씩 점진적으로 늘려가며(지수적 증가)&lt;/b&gt; 대기한 뒤 요청하는 지수 백오프 방식이 글로벌 엔지니어링 표준입니다. 여기에 매번 미세한 랜덤 노이즈(Jitter)를 섞어주어 거래소 서버에 동시 요청이 몰리는 현상도 분산시킵니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  파이썬 지수 백오프 구현 예제&lt;/h3&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import time
import random
import ccxt

def call_api_with_backoff(exchange_func, *args, max_retries=5, base_delay=2, **kwargs):
    &quot;&quot;&quot;
    거래소 API 호출 실패 시 지수 백오프(Exponential Backoff)와 Jitter를 적용하여 재시도합니다.
    - exchange_func: 실행할 CCXT API 함수 (예: exchange.fetch_ticker)
    - max_retries: 최대 재시도 횟수
    - base_delay: 최초 재시도 대기 시간(초)
    &quot;&quot;&quot;
    retries = 0
    while retries &amp;lt; max_retries:
        try:
            # API 함수 실행
            return exchange_func(*args, **kwargs)
        except (ccxt.NetworkError, ccxt.RequestTimeout, ccxt.DDoSProtection) as e:
            retries += 1
            if retries &amp;gt;= max_retries:
                print(f&quot;[API 경고] 최대 재시도 횟수({max_retries}회)를 초과했습니다. 호출 포기: {e}&quot;)
                raise e
            
            # 대기 시간 계산: base_delay * (2 ^ (retries - 1)) + random_jitter
            delay = base_delay * (2 ** (retries - 1))
            jitter = random.uniform(0.1, 1.0)
            final_delay = delay + jitter
            
            print(f&quot;[API 에러] {e} 발생. {final_delay:.2f}초 후 재시도합니다. (시도 {retries}/{max_retries})&quot;)
            time.sleep(final_delay)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 봇 프로세스 자가 치유(Self-Healing) 쉘 스크립트 구축&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 내부에서 아무리 에러를 방어해도, 메모리 부족(OOM)이나 OS 수준의 강제 종료로 봇 자체가 완전히 꺼지는 것까지는 방어할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 서버의 메모리(512MB RAM) 상황을 고려하여, 무거운 Node.js 기반의 프로세스 관리자(PM2) 대신 &lt;b&gt;리눅스 표준 크론탭(Crontab)과 가벼운 쉘 스크립트&lt;/b&gt;의 조합으로 봇의 생사 여부를 1분마다 모니터링하고 자동으로 깨워내는 시스템을 조립합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 1 단계: 자가 치유 쉘 스크립트 작성 (&lt;code&gt;check_bot.sh&lt;/code&gt;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 &lt;code&gt;/home/ubuntu/bot/&lt;/code&gt; 경로에 아래 내용으로 &lt;code&gt;check_bot.sh&lt;/code&gt; 파일을 생성합니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;#!/bin/bash

# [CRITICAL] 크론탭은 아주 극소량의 시스템 환경변수만 물고 작동합니다.
# 실행 파일 경로(PATH)를 명시적으로 주입하지 않으면 python3 명령어를 찾지 못할 수 있습니다.
export PATH=&quot;/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin&quot;

# 자동매매 봇 실행 스크립트의 경로 및 프로세스 명칭 정의
BOT_DIR=&quot;/home/ubuntu/bot&quot;
PROCESS_NAME=&quot;main.py&quot;
LOG_FILE=&quot;$BOT_DIR/realtime_trade.log&quot;

cd &quot;$BOT_DIR&quot; || exit

# [TIP] 만약 가상환경(venv) 내부에서 패키지를 설치해 구동하는 구조라면,
# 아래와 같이 가상환경 폴더의 인터프리터 경로를 직접 타겟팅해야 모듈 누락 에러가 나지 않습니다.
# PYTHON_EXEC=&quot;$BOT_DIR/venv/bin/python3&quot;
PYTHON_EXEC=&quot;python3&quot;

# 프로세스가 가동 중인지 검사
# grep -v grep을 통해 grep 명령 자체의 프로세스는 검색 결과에서 제외시킵니다.
if ! ps aux | grep &quot;$PROCESS_NAME&quot; | grep -v grep &amp;gt; /dev/null
then
    # 프로세스가 죽어 있다면
    echo &quot;[$(date)] 자동매매 봇이 중단된 상태입니다. 재시작을 시도합니다.&quot; &amp;gt;&amp;gt; &quot;$BOT_DIR/self_healing.log&quot;
    
    # nohup을 사용해 백그라운드로 봇 가동
    nohup $PYTHON_EXEC -u &quot;$PROCESS_NAME&quot; &amp;gt; &quot;$LOG_FILE&quot; 2&amp;gt;&amp;amp;1 &amp;amp;
    
    echo &quot;[$(date)] 봇이 성공적으로 재구동되었습니다.&quot; &amp;gt;&amp;gt; &quot;$BOT_DIR/self_healing.log&quot;
else
    # 프로세스가 잘 돌고 있다면 아무것도 하지 않음
    exit 0
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트 작성이 완료되면 터미널에서 실행 권한을 부여합니다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;chmod +x /home/ubuntu/bot/check_bot.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 2 단계: 크론탭(Crontab)에 1분 주기 감시 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스 스케줄러인 크론탭을 열어 1분마다 위 스크립트가 알아서 실행되도록 설정합니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;# 크론탭 설정 편집기 실행
crontab -e
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;편집기 최하단에 아래 설정을 입력하고 저장합니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# 1분마다 check_bot.sh를 실행하여 봇의 생존 상태를 확인하고, 죽었다면 예토전생시킴
* * * * * /home/ubuntu/bot/check_bot.sh &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 실전 예외 방어 설계: CCXT 예외 핸들링 구조 표준&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 매매 루프 내부에서 개별 예외를 계층적으로 묶어 봇 전체가 폭사하는 일을 원천 차단하는 예외 블록 설계의 표준 패턴입니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import sys

def main_trading_loop(exchange, db):
    while True:
        try:
            # 1. 1분마다 가격 분석 및 매매 신호 연산
            # call_api_with_backoff 함수를 사용해 안전하게 가격 로드
            ticker = call_api_with_backoff(exchange.fetch_ticker, 'BTC/USDT')
            
            # 매매 전략 판단 및 주문 접수 로직 실행...
            # (생략)
            
            time.sleep(60) # 1분 루프 주기
            
        except ccxt.InsufficientFunds as e:
            # 잔고 부족 에러: 더 이상 거래가 불가능하므로 관리자에게 경고 후 루프를 중단하거나 대기
            print(f&quot;[  심각 - 잔고 부족] 주문을 낼 예수금이 부족합니다: {e}&quot;)
            # 텔레그램 경고 메시지 송신 로직 연동
            time.sleep(300) # 경고가 도배되지 않도록 5분간 대기
            
        except ccxt.InvalidOrder as e:
            # 주문 요건 오류: 수수료나 최소 주문 수량 기준 미달 등
            print(f&quot;[주문 오류] 유효하지 않은 주문 조건입니다: {e}&quot;)
            time.sleep(60)
            
        except Exception as e:
            # 그 외의 모든 런타임 에러(네트워크 단절, DB 락 충돌 등)
            print(f&quot;[시스템 예외] 예상치 못한 오류 발생: {e}&quot;)
            # 시스템이 꼬여서 무한 에러가 날 수 있으므로 안전을 위해 10초 대기 후 재시도
            time.sleep(10)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚠️ 리스크 경고: 무한 재시도의 덫과 스팸 늪&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;무한 에러 루프의 텔레그램 스팸&lt;/b&gt;: 시스템 오류가 발생했을 때 텔레그램으로 즉각 에러 로그를 전송하게 코딩해 두면, 봇이 무한히 에러를 내며 재시도하는 과정에서 &lt;b&gt;스마트폰이 1초에 수십 통의 텔레그램 알림 폭탄&lt;/b&gt;을 받게 됩니다. 결국 텔레그램 API 마저 호출 제한에 걸려 정작 중요한 킬스위치 신호조차 도달하지 못할 수 있습니다. 에러 알림은 동일 에러의 경우 최소 10분이나 1시간의 간격을 두고 누적 전송하도록 '스로틀링(Throttling)' 장치를 반드시 엮어야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의도치 않은 좀비 프로세스 복제&lt;/b&gt;: &lt;code&gt;check_bot.sh&lt;/code&gt; 내부에서 프로세스명을 검색할 때, 파일명 검출 필터가 헐거우면(예: 파이썬 전체 프로세스를 검색하는 등) 봇이 이미 실행 중임에도 죽었다고 잘못 판정하여 동일한 봇이 백그라운드에 2개, 3개 겹쳐서 기동되는 좀비 프로세스 복제 참사가 일어날 수 있습니다. 이 경우 중복 주문이 들어가 자산이 순식간에 녹아내리게 되므로, &lt;code&gt;ps aux&lt;/code&gt; 검사 조건에 봇 고유의 스크립트 파일명을 아주 정밀하게 명시해 주어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;에필로그: 다음 단계는 '실전 배포 총정리 및 최종 가이드!'&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 앞에서 유연하게 후퇴하는 지수 백오프와, 비명횡사한 봇을 60초 내에 강제로 일으켜 세우는 자가 치유 파수꾼(Crontab)이 완성되었습니다. 이제 진정한 무인 운용 인프라가 마침내 구축되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다음 편 예고 체크리스트&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] My AI Stories 시즌 3 대단원의 마침표&lt;/li&gt;
&lt;li&gt;[ ] 24시간 실전 클라우드 매매 자동화 시스템의 전체 아키텍처 다이어그램 총정리&lt;/li&gt;
&lt;li&gt;[ ] 실전 배포 시 잊지 말아야 할 필수 10대 체크리스트&lt;/li&gt;
&lt;li&gt;[ ] 향후 데이터 사이언스 및 머신러닝 모델 탑재를 위한 고도화 로드맵 제안&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편에서는 그동안 우리가 다져온 24시간 실전 자동매매 봇의 인프라와 로직을 한눈에 볼 수 있게 정립하고, 최종 배포를 완성하는 **'시즌 3 최종화: 24시간 실전 배포 총정리 및 무한 성장 로드맵 가이드'**로 찾아오겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가장 튼튼한 시스템을 구축한 자가 결국 승리의 열매를 땁니다. 오늘 글이 흥미로우셨다면 구독과 따뜻한 공감 댓글 부탁드립니다. 감사합니다!&lt;/b&gt;&lt;/p&gt;</description>
      <author>myaistories</author>
      <guid isPermaLink="true">https://myaistories.tistory.com/28</guid>
      <comments>https://myaistories.tistory.com/28#entry28comment</comments>
      <pubDate>Wed, 27 May 2026 06:55:28 +0900</pubDate>
    </item>
    <item>
      <title>[시즌 3 - 6화] &amp;quot;계란을 나누고 비중을 맞춘다&amp;quot; 다중 자산(Multi-Asset) 분산 매매와 정적 켈리(Kelly) 공식 리밸런싱</title>
      <link>https://myaistories.tistory.com/27</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 지난 연재에서는 외부 야외 활동 중에도 스마트폰으로 가상 서버의 봇을 간편하게 통제하고, 긴급 상황 시 포지션을 즉시 털어버릴 수 있는 '텔레그램 킬스위치 제어' 모듈을 개발했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 조종 장치가 생기니 한결 마음이 든든해졌지만, 여전히 우리의 투자 계좌는 커다란 구조적 위험에 직면해 있습니다. 만약 비트코인 단 한 가지만 거래하다가 갑자기 시장이 흔들리거나 예기치 못한 차트 노이즈에 휩쓸리면, 아무리 정교한 단일 전략이라도 며칠 만에 수차례 손절을 겪으며 시드가 크게 상해버리기 때문입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&quot;비트코인, 이더리움, 솔라나처럼 여러 코인에 나누어 매매를 돌리면 변동성이 낮아질 텐데, 각 코인마다 대체 내 시드의 몇 %씩 배정해서 베팅해야 하는 거지?&quot;&lt;/i&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 자금 분배 문제를 직관이나 감에 의존하지 않고, 수학적으로 내 전략의 **승률(Win Rate)**과 **손익비(Win/Loss Ratio)**에 맞추어 최적의 배팅 비율을 산출해 주는 도구가 바로 **켈리 공식(Kelly Criterion)**입니다. 오늘 연재에서는 단일 자산 켈리를 넘어 여러 자산군에 안전하게 시드를 나누고 재조정하는 다중 자산 정적 켈리 배분 엔진을 개발해 보겠습니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 다중 자산 켈리 공식의 수학적 토대&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;켈리 공식은 매번 거래할 때마다 파산 확률을 0%로 통제하면서 내 자산의 기하급수적 성장률(Geometric Growth)을 극대화하는 투자 비중($f^*$)을 구하는 공식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$f^* = \frac{p}{a} - \frac{q}{b} = \frac{bp - q}{b}$$&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$p$: 과거 매매 승률 (예: 55% = 0.55)&lt;/li&gt;
&lt;li&gt;$q$: 과거 매매 패율 ( $1 - p$ )&lt;/li&gt;
&lt;li&gt;$b$: 순 손익비 (평균 수익금 / 평균 손실금)&lt;/li&gt;
&lt;li&gt;$a$: 손실 시 손실률 (일반적으로 봇의 레버리지가 1배이고 전액 손실이 아니므로, 손절 비율 등 설정에 따름. 레버리지 1배 기준 청산 시 $a=1$)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ 실전 필터: Fractional Kelly (분할 켈리)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수학적 켈리 공식은 이론적으로 최고 효율을 내지만, 시장의 실제 확률 분포가 과거 데이터와 완벽히 같지 않기 때문에 켈리 공식이 계산해낸 값 그대로 베팅하면 계좌 변동성(Drawdown)이 극도로 커집니다. 따라서 실전 트레이딩에서는 계산된 켈리 값의 25%~50%만 보수적으로 베팅하는 &lt;b&gt;'하프 켈리(Half-Kelly, 0.5)'&lt;/b&gt; 또는 &lt;b&gt;'쿼터 켈리(Quarter-Kelly, 0.25)'&lt;/b&gt; 요율을 곱해 방어력을 높여야 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 파이썬 다중 자산 켈리 자금 분배 모듈 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 자산(BTC, ETH, SOL)에 대해 개별 백테스팅 데이터를 기반으로 켈리 베팅 비중을 산출하고, 전체 가용한 자산 범위 내로 비율을 정규화(Normalization)하는 파이썬 모듈입니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;class MultiAssetKellyAllocator:
    def __init__(self, assets_stats, fractional_multiplier=0.25):
        &quot;&quot;&quot;
        - assets_stats: 각 자산의 백테스팅 승률과 손익비를 담은 딕셔너리
          예시: {
             'BTC/USDT': {'win_rate': 0.54, 'profit_loss_ratio': 1.4},
             'ETH/USDT': {'win_rate': 0.52, 'profit_loss_ratio': 1.5},
             'SOL/USDT': {'win_rate': 0.48, 'profit_loss_ratio': 1.8}
          }
        - fractional_multiplier: 변동성 제어를 위한 분할 켈리 배수 (기본 쿼터 켈리 0.25)
        &quot;&quot;&quot;
        self.assets_stats = assets_stats
        self.multiplier = fractional_multiplier

    def calculate_kelly_fractions(self):
        &quot;&quot;&quot;각 개별 자산의 raw 켈리 비율 계산&quot;&quot;&quot;
        raw_fractions = {}
        for symbol, stats in self.assets_stats.items():
            p = stats['win_rate']
            q = 1 - p
            b = stats['profit_loss_ratio']
            
            if b &amp;lt;= 0:
                raw_fractions[symbol] = 0.0
                continue
                
            # 켈리 공식 계산: f* = (bp - q) / b
            f_star = (b * p - q) / b
            
            # 켈리 값이 음수(기댓값이 마이너스인 불리한 전략)이면 베팅 금지
            raw_fractions[symbol] = max(0.0, f_star)
            
        return raw_fractions

    def allocate_weights(self, total_balance):
        &quot;&quot;&quot;
        가용한 총잔고를 기준하여 각 자산군별 최종 진입 금액(USDT) 산출
        자산 간 합이 100%를 초과하는 레버리지 방지를 위한 정규화 수행
        &quot;&quot;&quot;
        raw_fractions = self.calculate_kelly_fractions()
        
        # 1. 분할 켈리 배수 적용 (예: 계산값에 0.25를 곱해 안정화)
        adjusted_fractions = {k: v * self.multiplier for k, v in raw_fractions.items()}
        
        # 2. 총 배분 비율의 합이 가용 자산 한도(예: 현금의 90%)를 넘지 않도록 정규화
        total_fraction_sum = sum(adjusted_fractions.values())
        max_allowed_fraction = 0.90  # 계좌 안전 마진 10%를 남겨둠
        
        final_allocations = {}
        if total_fraction_sum &amp;gt; max_allowed_fraction:
            # 합이 한도를 넘어서면 각 비중을 비례적으로 축소(정규화)
            scale = max_allowed_fraction / total_fraction_sum
            for symbol, val in adjusted_fractions.items():
                final_allocations[symbol] = val * scale
        else:
            final_allocations = adjusted_fractions
            
        # 3. 비중을 기준으로 최종 할당 USDT 계산
        allocations_in_usdt = {}
        for symbol, weight in final_allocations.items():
            allocations_in_usdt[symbol] = round(total_balance * weight, 2)
            
        return final_allocations, allocations_in_usdt

# [고급 기능] 데이터베이스(DB) 기반 실시간 롤링 승률 및 손익비 산출
# 과거 정적 백테스트 값에 고이지 않고, 봇이 최근 체결한 실제 거래 데이터를 바탕으로
# 승률과 손익비를 실시간 재 계산하여 켈리 비중을 동적 리밸런싱합니다.
def get_rolling_stats_from_db(db_path=&quot;trade_bot.db&quot;, symbol=&quot;BTC/USDT&quot;, rolling_n=30):
    &quot;&quot;&quot;
    SQLite 데이터베이스에서 최근 N개의 매매 내역을 조회하여 승률과 손익비를 계산합니다.
    &quot;&quot;&quot;
    try:
        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        
        # 최근 N개의 완료된 거래 이력 로드 (BUY -&amp;gt; SELL 쌍의 손익 확인용)
        cursor.execute(&quot;&quot;&quot;
            SELECT side, price, amount FROM trades 
            WHERE symbol = ? 
            ORDER BY id DESC LIMIT ?
        &quot;&quot;&quot;, (symbol, rolling_n * 2))
        rows = cursor.fetchall()
        conn.close()
        
        # 거래 기록이 부족할 때 사용할 디폴트 백테스트 기초 통계치 반환
        if len(rows) &amp;lt; 4:
            return {'win_rate': 0.53, 'profit_loss_ratio': 1.4}
            
        profits = []
        losses = []
        
        # 매수(BUY)와 매도(SELL) 주문 쌍 매칭 후 수익률 분석
        for i in range(0, len(rows) - 1, 2):
            trade_now = rows[i]   # 가장 최근
            trade_prev = rows[i+1] # 그 이전
            
            # 매수 진입 후 매도 청산 케이스
            if trade_now[0] == 'SELL' and trade_prev[0] == 'BUY':
                pnl = (trade_now[1] - trade_prev[1]) / trade_prev[1]
                if pnl &amp;gt; 0:
                    profits.append(pnl)
                else:
                    losses.append(abs(pnl))
                    
        total = len(profits) + len(losses)
        if total == 0:
            return {'win_rate': 0.53, 'profit_loss_ratio': 1.4}
            
        win_rate = len(profits) / total
        avg_profit = sum(profits) / len(profits) if profits else 0.0
        avg_loss = sum(losses) / len(losses) if losses else 0.001
        
        profit_loss_ratio = avg_profit / avg_loss if avg_loss &amp;gt; 0 else 1.0
        
        # 통계 아웃라이어 수치 제한 (클리핑)
        profit_loss_ratio = max(0.5, min(profit_loss_ratio, 3.0))
        
        return {'win_rate': round(win_rate, 2), 'profit_loss_ratio': round(profit_loss_ratio, 2)}
    except Exception as e:
        print(f&quot;[동적 통계] 데이터 조회 에러 (기본값 폴백): {e}&quot;)
        return {'win_rate': 0.53, 'profit_loss_ratio': 1.4}

# 실행 및 검증
if __name__ == &quot;__main__&quot;:
    import sqlite3
    
    # 1. 대상 자산군 정의
    target_symbols = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT']
    
    # 2. 각 자산의 실시간 통계치를 DB로부터 롤링 집계
    db_path = &quot;trade_bot.db&quot;
    dynamic_assets_stats = {}
    
    for symbol in target_symbols:
        # DB에서 최근 30개 거래 기록 기반으로 실시간 승률/손익비 추출
        stats = get_rolling_stats_from_db(db_path=db_path, symbol=symbol, rolling_n=30)
        dynamic_assets_stats[symbol] = stats
        
    print(&quot;==== DB 수집 실시간 매매 통계 ====&quot;)
    for symbol, stats in dynamic_assets_stats.items():
        print(f&quot;&amp;bull; {symbol}: 승률 {stats['win_rate']*100:.0f}% | 손익비 {stats['profit_loss_ratio']:.2f}&quot;)
    
    # 3. 쿼터 켈리(0.25) 적용 분배 엔진 인스턴스 생성 및 연산
    allocator = MultiAssetKellyAllocator(dynamic_assets_stats, fractional_multiplier=0.25)
    
    # 현재 내 지갑 총자산이 10,000 USDT라고 가정
    wallet_balance = 10000.0
    weights, amounts = allocator.allocate_weights(wallet_balance)
    
    print(&quot;\n==== 다중 자산 켈리 자금 배분 결과 ====&quot;)
    print(f&quot;총 자산: {wallet_balance} USDT&quot;)
    for symbol in target_symbols:
        print(f&quot;[{symbol}] 할당 비율: {weights[symbol]*100:.2f}% | 배팅 금액: {amounts[symbol]:.2f} USDT&quot;)
        
    print(f&quot;남겨진 미배분 여유 현금: {wallet_balance - sum(amounts.values()):.2f} USDT&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 다중 자산 분산 배매 시 주의할 함정: 상관관계(Correlation)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수학적으로 깔끔하게 자금이 쪼개졌으나, 실제 암호화폐 시장에서 기계적으로 이를 실행할 때는 매우 중대한 **'자산 간 동조화 현상'**을 마주하게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;상관관계 1에 수렴하는 동반 폭락&lt;/b&gt;: 비트코인, 이더리움, 솔라나는 시장 급락 국면에서 거의 80%~95%의 유사한 상관관계(Correlation)를 보이며 동시에 떨어집니다. 즉, 세 코인의 백테스팅 데이터가 아무리 훌륭해도 시장 전체가 하락장으로 꺾이면 세 자산 모두 동시에 손절 신호를 뱉어내게 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;포트폴리오의 실질 Drawdown 증폭&lt;/b&gt;: 개별 코인 켈리로 10%씩 안전하게 나눴다 하더라도, 3개 코인이 동시에 손절하면 내 전체 시드는 순식간에 30%에 해당하는 타격을 한 번에 입을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결 방안&lt;/b&gt;: 자산 간 상관관계 매트릭스를 추가로 검토하여 동조화가 강한 코인들끼리 묶여 있을 때는 분할 켈리 배수를 &lt;code&gt;0.25&lt;/code&gt;에서 &lt;code&gt;0.15&lt;/code&gt; 등으로 더 보수적으로 깎아야 합니다. 혹은 비가상화폐 자산(예: 골드, 달러 선물 등)을 포트폴리오에 섞어 상관관계를 억제하는 전략적 확장이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;에필로그: 다음 단계는 '시스템 모니터링 및 자가치유!'&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다중 자산으로 전선을 넓히고, 자산을 안전하고 수학적인 비율로 배분하는 고도화된 트레이딩 시스템의 뼈대가 갖추어졌습니다. 이제 분산 투자를 통해 계좌 우상향의 안정성을 한 차원 끌어올릴 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다음 편 예고 체크리스트&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] 클라우드 서버에서 봇이 소리 소문 없이 중단되는 현상(Memory Leak, API 에러) 분석&lt;/li&gt;
&lt;li&gt;[ ] 봇이 죽었는지 주기적으로 감시하고 자동으로 살려내는 PM2 / systemd 환경 세팅&lt;/li&gt;
&lt;li&gt;[ ] 단시간 과도한 API 호출로 인한 거래소 밴(IP Ban / Rate Limit)을 막는 Exponential Backoff(지수 백오프) 코딩&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편에서는 24시간 안정 구동 시스템의 최종 끝판왕인 &lt;b&gt;'서버 자동 생존 관리: API Rate Limit 방어 및 봇 자가 치유(Self-Healing) 프로세스 구축'&lt;/b&gt; 편으로 찾아오겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자금 관리를 모르는 트레이딩은 도박일 뿐입니다. 오늘 내용이 가치 있었다면 이웃 신청과 댓글로 힘을 보태주세요. 감사합니다!&lt;/b&gt;&lt;/p&gt;</description>
      <author>myaistories</author>
      <guid isPermaLink="true">https://myaistories.tistory.com/27</guid>
      <comments>https://myaistories.tistory.com/27#entry27comment</comments>
      <pubDate>Wed, 27 May 2026 06:54:58 +0900</pubDate>
    </item>
    <item>
      <title>[시즌 3 - 5화] &amp;quot;손안의 무선 조종기&amp;quot; 텔레그램(Telegram) 양방향 긴급 제어와 실시간 킬스위치(Kill-Switch) 설계</title>
      <link>https://myaistories.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 지난 연재에서는 백테스팅의 이상과 실전 거래의 현실 간 극적인 오차를 만드는 슬리피지(Slippage)와 수수료 등 거래 마찰 비용 모델링을 분석하고, 백테스터에 이를 보정하는 기법을 다루었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 봇은 24시간 가상 서버(AWS 라이트세일)에서 돌아가고 있고, 데이터베이스로 기억을 보존하며, 모의투자로 검증 단계도 진행 중입니다. 하지만 한 가지 중요한 불안감이 여전히 엄습합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&quot;밖에서 밥을 먹거나 회사 업무를 보던 중에 시장이 갑자기 폭락하면 어떻게 하지? 당장 집에 가서 PC를 켜거나 모바일 SSH 터미널로 복잡한 명령어를 쳐서 꺼야 하나?&quot;&lt;/i&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 서버에서 도는 봇은 원격 제어가 불가능하면 일종의 '통제 불능 폭주 기관차'가 될 위험이 있습니다. 이동 중이나 취침 중에도 스마트폰으로 간편하게 봇의 작동 상황을 보고받고, 필요할 때 즉시 거래를 멈추거나 보유 자산을 시장가로 전량 매도해 자금을 회수할 수 있는 **'원격 제어 킬스위치(Kill-Switch)'**가 반드시 필요한 이유입니다. 오늘 연재에서는 가장 친숙한 메신저인 텔레그램을 활용하여 양방향 긴급 제어 모듈을 제작해 보겠습니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 텔레그램 봇 개설 및 인증 토큰 저장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텔레그램은 개발자 친화적인 API를 제공하여 간단한 HTTP 요청만으로 메시지를 주고받을 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  텔레그램 봇 토큰 및 Chat ID 획득&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;텔레그램 검색창에 **&lt;code&gt;@BotFather&lt;/code&gt;**를 검색하여 대화를 시작합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/newbot&lt;/code&gt; 명령어를 입력하고, 봇 이름과 사용자 이름(Username)을 설정합니다.&lt;/li&gt;
&lt;li&gt;생성이 완료되면 고유한 &lt;b&gt;&lt;code&gt;HTTP API Token&lt;/code&gt;&lt;/b&gt;(예: &lt;code&gt;123456789:ABCdefGhI...&lt;/code&gt;)이 발급됩니다.&lt;/li&gt;
&lt;li&gt;내 봇과 대화를 시작한 뒤, 웹브라우저 주소창에 &lt;code&gt;https://api.telegram.org/bot&amp;lt;본인의토큰&amp;gt;/getUpdates&lt;/code&gt;를 입력하고 접속하여 메시지를 보낸 내 계정의 &lt;code&gt;chat&lt;/code&gt; -&amp;gt; &lt;b&gt;&lt;code&gt;id&lt;/code&gt;&lt;/b&gt;(숫자 값)를 기록합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 토큰과 Chat ID를 기존의 &lt;code&gt;token.json&lt;/code&gt; 설정 파일에 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;TELEGRAM_TOKEN&quot;: &quot;YOUR_BOT_TOKEN_HERE&quot;,
  &quot;AUTHORIZED_CHAT_ID&quot;: 123456789
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 보안성이 보장된 텔레그램 양방향 제어 스크립트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누구나 내 봇의 텔레그램 방에 들어와 명령어를 입력할 수 있으면 해킹의 위험이 매우 큽니다. 따라서 오직 **&lt;code&gt;AUTHORIZED_CHAT_ID&lt;/code&gt;**로 지정된 주인 계정에서 온 명령만 해독하여 실행하는 필터링 메커니즘을 적용하여 양방향 제어 코드를 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가볍고 서버 메모리를 거의 먹지 않는 &lt;code&gt;requests&lt;/code&gt; 모듈의 롱 폴링(Long Polling) 방식을 차용하여 텔레그램 명령 대기 모듈을 조립해 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import time
import requests
import threading

class TelegramController:
    def __init__(self, token, auth_chat_id, exchange_client, db_client):
        self.token = token
        self.auth_chat_id = auth_chat_id
        self.exchange = exchange_client
        self.db = db_client
        self.last_update_id = 0
        self.is_running = True
        self.bot_paused = False # 매매 일시정지 플래그
        
    def send_message(self, text):
        &quot;&quot;&quot;지정된 마스터 텔레그램 방으로 알림 발송&quot;&quot;&quot;
        url = f&quot;https://api.telegram.org/bot{self.token}/sendMessage&quot;
        payload = {&quot;chat_id&quot;: self.auth_chat_id, &quot;text&quot;: text}
        try:
            requests.post(url, json=payload, timeout=10)
        except Exception as e:
            print(f&quot;[텔레그램] 메시지 전송 실패: {e}&quot;)

    def start_listening(self):
        &quot;&quot;&quot;백그라운드 스레드에서 메시지 리스너 실행&quot;&quot;&quot;
        t = threading.Thread(target=self._polling_loop, daemon=True)
        t.start()
        print(&quot;[텔레그램] 실시간 명령 수신 대기 스레드가 가동되었습니다.&quot;)

    def _polling_loop(self):
        &quot;&quot;&quot;실시간 메시지 수신 무한 루프 (Long Polling)&quot;&quot;&quot;
        url = f&quot;https://api.telegram.org/bot{self.token}/getUpdates&quot;
        
        while self.is_running:
            try:
                params = {&quot;offset&quot;: self.last_update_id + 1, &quot;timeout&quot;: 20}
                # 롱 폴링 요청 전송
                res = requests.get(url, params=params, timeout=25)
                
                # HTTP 상태 코드 점검 (상태 코드 200이 아닐 시 5초간 슬립 후 재시도)
                if res.status_code != 200:
                    print(f&quot;[텔레그램] HTTP 에러 ({res.status_code})로 인한 연결 실패. 5초 후 재시도합니다.&quot;)
                    time.sleep(5)
                    continue
                
                # JSON 파싱 유효성 검증
                response = res.json()
                if not response.get(&quot;ok&quot;):
                    continue
                    
                for update in response.get(&quot;result&quot;, []):
                    self.last_update_id = update[&quot;update_id&quot;]
                    message = update.get(&quot;message&quot;, {})
                    chat_id = message.get(&quot;chat&quot;, {}).get(&quot;id&quot;)
                    text = message.get(&quot;text&quot;, &quot;&quot;).strip()
                    
                    # 1. 인가된 사용자만 접근할 수 있도록 차단
                    if chat_id != self.auth_chat_id:
                        print(f&quot;[텔레그램 경고] 비인가 접근 감지! Chat ID: {chat_id}&quot;)
                        continue
                    
                    # 2. 명령어 해석 및 분기 처리
                    self._handle_command(text)
                    
            except requests.exceptions.RequestException as e:
                print(f&quot;[텔레그램] API 통신 장애 발생: {e}. 5초 대기 후 재시도.&quot;)
                time.sleep(5)
            except Exception as e:
                print(f&quot;[텔레그램] 폴링 루프 일반 예외: {e}&quot;)
                time.sleep(5)

    def _handle_command(self, command):
        &quot;&quot;&quot;수신된 텔레그램 명령어 분기 실행&quot;&quot;&quot;
        if command == &quot;/status&quot;:
            # 봇 상태 및 계좌 잔고 파악
            try:
                balance = self.exchange.fetch_balance()
                usdt_free = balance.get('USDT', {}).get('free', 0.0)
                status_msg = (
                    f&quot;  [봇 가동 현황]\n&quot;
                    f&quot;&amp;bull; 매매 플래그: {'PAUSED (일시정지)' if self.bot_paused else 'RUNNING (정상가동)'}\n&quot;
                    f&quot;&amp;bull; 보유 현금: {usdt_free:.2f} USDT\n&quot;
                    f&quot;&amp;bull; 모니터링 코인: BTC/USDT&quot;
                )
                self.send_message(status_msg)
            except Exception as e:
                self.send_message(f&quot;❌ 상태 조회 중 오류: {e}&quot;)
                
        elif command == &quot;/pause&quot;:
            # 자동 매매 진입 일시 정지
            self.bot_paused = True
            self.send_message(&quot;⏸️ 자동 매매 진입 신호가 일시정지되었습니다. (보유 자산은 유지됩니다.)&quot;)
            
        elif command == &quot;/resume&quot;:
            # 자동 매매 재개
            self.bot_paused = False
            self.send_message(&quot;▶️ 자동 매매 신호 감지가 재개되었습니다.&quot;)
            
        elif command == &quot;/panic&quot;:
            # 긴급 비상 킬스위치 (모든 보유 자산 시장가 즉시 매도 후 시스템 일시중지)
            self.send_message(&quot;  [긴급 제어] 비상 킬스위치 발동! 보유 포지션 즉시 시장가 청산을 시도합니다.&quot;)
            self._execute_panic_sell()
            
        else:
            self.send_message(&quot;❓ 알 수 없는 명령어입니다.\n사용 가능 명령어: /status, /pause, /resume, /panic&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 긴급 비상 킬스위치(Kill-Switch) 청산 로직 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 &lt;code&gt;_execute_panic_sell()&lt;/code&gt; 함수는 시장 급변 사태나 시스템 오작동이 감지될 때 최우선적으로 실행될 킬스위치 로직을 담당합니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;    def _execute_panic_sell(self):
        &quot;&quot;&quot;보유한 모든 암호화폐 포지션을 무조건 시장가로 청산 (3회 재시도 보강)&quot;&quot;&quot;
        max_attempts = 3
        for attempt in range(1, max_attempts + 1):
            try:
                # 1. 거래소 잔고 조회
                balance = self.exchange.fetch_balance()
                btc_total = balance.get('BTC', {}).get('total', 0.0)
                
                # 최소 거래 가능 한도(예: 0.0001 BTC) 이상일 때만 매도 진행
                if btc_total &amp;gt; 0.0001:
                    print(f&quot;[킬스위치 시도 {attempt}/{max_attempts}] {btc_total} BTC 시장가 매도 주문 전송...&quot;)
                    
                    # 시장가(market)로 긴급 청산
                    order = self.exchange.create_order(
                        symbol=&quot;BTC/USDT&quot;,
                        type=&quot;market&quot;,
                        side=&quot;sell&quot;,
                        amount=btc_total
                    )
                    
                    # DB에 비상 매도 거래 기록 기록
                    self.db.record_trade(
                        symbol=&quot;BTC/USDT&quot;,
                        side=&quot;SELL_PANIC&quot;,
                        price=order.get('price', 0.0),
                        amount=btc_total,
                        fee=btc_total * order.get('price', 0.0) * 0.001,
                        order_id=order.get('id', 'PANIC')
                    )
                    
                    self.bot_paused = True # 추가 오작동 방지를 위해 매매 강제 일시 정지
                    self.send_message(f&quot;✅ 긴급 청산 완료! {btc_total} BTC 전량 시장가 매도 처리되었습니다. 시스템은 PAUSE 상태로 전환됩니다.&quot;)
                    return
                else:
                    self.send_message(&quot;⚠️ 보유 중인 비트코인 잔고가 없거나 최소 주문 수량 미만입니다.&quot;)
                    return
            except Exception as e:
                print(f&quot;[킬스위치 시도 {attempt} 실패] 에러: {e}&quot;)
                if attempt &amp;lt; max_attempts:
                    time.sleep(2) # 재시도 전 2초 대기
                else:
                    error_msg = f&quot;❌ [긴급 청산 최종 실패] 3회 시도 모두 실패했습니다: {e}\n즉시 원격 서버 수동 접속을 권장합니다!&quot;
                    self.send_message(error_msg)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚠️ 리스크 경고: 원격 텔레그램 통제 시 발생할 수 있는 덫&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스마트폰 하나로 봇을 다룰 수 있어 대단히 편리하지만, 무선 제어 시스템은 다음과 같은 한계가 있어 맹신하면 안 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;메신저 서버의 레이턴시(지연)&lt;/b&gt;: 텔레그램 폴링 스레드는 평균 1~3초의 텀을 두고 주기적으로 신호를 긁어옵니다. 급변하는 시장의 1초는 매우 큰 시간이며, 텔레그램 해외 망 연결이 지연될 경우 명령어가 접수되는 데 수십 초 이상 지체될 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다중 명령어 중복 입력 방지&lt;/b&gt;: 스마트폰 화면에서 마음이 급해져서 &lt;code&gt;/panic&lt;/code&gt; 버튼을 수차례 연속해서 누르면, 첫 번째 주문이 채 실행 완료되기도 전에 두 번째 공매도 주문이 중복 접수되어 계좌에 의도치 않은 숏 포지션이 열리는 끔찍한 오작동이 터질 수 있습니다. 명령어 처리 중에는 **'명령 락(Lock) 플래그'**를 활성화해 한 번에 하나의 비상 조치만 수행되도록 방어해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인터넷 음영 구역&lt;/b&gt;: 엘리베이터나 터널, 혹은 통신망 장애 구간에 진입하여 스마트폰 인터넷이 끊겼을 때는 봇의 폭주 상태를 인지하거나 제어할 방법이 전혀 없으므로, 메신저 연동에 전적으로 의지하기보다 봇 내부에 자체적인 알고리즘적 한도(예: 일일 최대 손실액 도달 시 자가 일시정지)를 중첩해 탑재해 놓는 것이 진정한 2중 안전장치입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;에필로그: 다음 단계는 '정적 켈리 공식 다중자산 자금 분배!'&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 봇에 손안의 안전 브레이크(텔레그램 킬스위치)를 완전히 장착하는 데 성공했습니다. 이제 외부에서도 마음 놓고 봇을 가동하며 모니터링할 수 있는 심리적 여유가 생겼습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다음 편 예고 체크리스트&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] 비트코인 하나만 거래할 때의 집중 리스크 극복하기&lt;/li&gt;
&lt;li&gt;[ ] 여러 개 코인(BTC, ETH, SOL)에 투자금을 합리적으로 쪼개는 포트폴리오 관리 기법&lt;/li&gt;
&lt;li&gt;[ ] 백테스팅 데이터 기반의 개별 자산 승률/손익비를 활용한 '다중 자산 켈리(Kelly) 공식 리밸런싱 알고리즘' 코딩&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 시간에는 리스크 관리의 꽃이자, 내 투자 시드를 쪼개 여러 알트코인에 스마트하게 배분하고 포트폴리오를 자동으로 재조정하는 **'정적 켈리 공식 기반 다중 자산 자금 배분 및 리밸런싱 모듈'**을 설계해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내 자산을 가장 안전하게 통제하는 트레이더가 끝까지 살아남습니다. 오늘 내용도 마음에 드셨다면 구독과 응원 부탁드립니다. 감사합니다!&lt;/b&gt;&lt;/p&gt;</description>
      <author>myaistories</author>
      <guid isPermaLink="true">https://myaistories.tistory.com/26</guid>
      <comments>https://myaistories.tistory.com/26#entry26comment</comments>
      <pubDate>Wed, 27 May 2026 06:54:30 +0900</pubDate>
    </item>
    <item>
      <title>[시즌 3 - 4화] &amp;quot;백테스트의 환상에서 깨어날 시간&amp;quot; 슬리피지(Slippage)와 거래 마찰비용 모델링</title>
      <link>https://myaistories.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 지난 연재에서 우리는 가상의 돈으로 실시간 거래소 호가에 연동하여 매수/매도 주문을 넣어보는 테스트넷 모의투자 연동까지 완성했습니다. 테스트넷 주문이 문제없이 돌아가는 모습을 보면 당장이라도 큰 수익이 날 것처럼 가슴이 웅장해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 단계에서 봇에 실물 자산을 충전하고 실전 투자를 시작하는 많은 초보 개발자들이 얼마 지나지 않아 심각한 현상을 겪게 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&quot;백테스팅 돌렸을 때는 분명 3개년 누적 수익률이 +120%였는데, 왜 실전 봇은 한 달 동안 거래할수록 잔고가 야금야금 줄어들지?&quot;&lt;/i&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 괴리의 주범이 바로 **'거래 마찰 비용(Transaction Friction)'**입니다. 백테스팅 화면 속 세상은 내가 사고 싶은 가격에 언제든 무한대로 체결되고 수수료가 없는 이상적인 무중력 공간이지만, 실전 거래소는 **주문 체결 지연(Latency), 수수료(Fees), 그리고 슬리피지(Slippage)**라는 강력한 마찰력이 작동하는 진흙탕이기 때문입니다. 오늘 연재에서는 이 마찰력을 정밀하게 모델링하여 백테스팅의 환상을 걷어내고 진짜 생존 가능한 전략을 빚어내는 방법을 알아보겠습니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 거래 마찰 비용의 세 가지 적: 수수료, 스프레드, 슬리피지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전 매매에서 발생하는 비용은 단순히 거래소가 떼어가는 수수료가 전부가 아닙니다. 아래 세 가지 요소를 정확히 계량화해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 거래 수수료 (Trading Fees)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Maker(지정가) vs Taker(시장가)&lt;/b&gt;: 지정가로 호가창에 주문을 걸어두고 체결을 기다리면 메이커 수수료가, 시장가로 즉시 체결시키면 테이커 수수료가 부과됩니다. 일반적으로 테이커 수수료가 2~5배 이상 비쌉니다.&lt;/li&gt;
&lt;li&gt;봇이 시장가 진입/지정가 청산 방식을 혼용한다면, 매수/매도 각각의 수수료 요율을 철저히 분리해서 모델링해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 호가 스프레드 (Bid-Ask Spread)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매수 호가(Bid)와 매도 호가(Ask) 사이의 간격을 의미합니다. 만약 비트코인 매도 1호가가 10,000달러이고 매수 1호가가 9,998달러라면, 진입하자마자 즉시 팔 경우 시작부터 -0.02%의 스프레드 손실을 안고 출발하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 슬리피지 (Slippage)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내가 주문을 전송한 시점과 실제 거래소 엔진에 접수되어 체결되는 찰나의 시간(약 50ms ~ 300ms) 사이에 가격이 변해 발생하는 괴리입니다. 특히 시장가 주문을 넣을 때, 호가창의 잔량이 부족하면 내 주문이 2호가, 3호가까지 긁어버리며 원래 의도보다 훨씬 나쁜 가격에 체결됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 파이썬을 활용한 슬리피지 및 수수료 시뮬레이션 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백테스터 엔진 내에서 매수/매도 신호가 발생했을 때, 단순 종가(Close)에 체결되는 것이 아니라 &lt;b&gt;슬리피지 패널티와 수수료 요율을 가산/차감&lt;/b&gt;하는 파이썬 모델링 예제입니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import pandas as pd
import numpy as np

def run_backtest_with_dynamic_friction(df, initial_seed=10000000, fee_rate=0.001, base_slippage=0.0002):
    &quot;&quot;&quot;
    거래 수수료와 ATR(변동성) 기반 동적 슬리피지를 반영한 정밀 백테스팅 시뮬레이션
    - fee_rate: 편도 거래 수수료 (예: 0.1% = 0.001)
    - base_slippage: 유동성이 좋은 평시의 기본 슬리피지 요율 (예: 0.02% = 0.0002)
    &quot;&quot;&quot;
    seed = initial_seed
    position = 0  # 0: 무포지션, 1: 매수 상태
    buy_price = 0.0
    
    # ATR(Average True Range) 지표 계산 (변동성 비례 슬리피지 계산용)
    if 'atr' not in df.columns:
        # 고가와 저가의 14봉 평균 차이로 간이 ATR 계산
        df['atr'] = (df['high'] - df['low']).rolling(14).mean()
        df['atr'] = df['atr'].fillna(df['close'] * 0.005) # 초기값 폴백
        
    balance_history = []
    
    for i in range(len(df)):
        current_close = df['close'].iloc[i]
        current_atr = df['atr'].iloc[i]
        signal = df['signal'].iloc[i]  # 1: 매수, -1: 매도, 0: 유지
        
        # [DYNAMIC SLIPPAGE MODELING]
        # 변동성(ATR / Price)이 높을수록 호가창이 발산하고 슬리피지가 급증하는 현상을 모델링합니다.
        # 기본 슬리피지에 현재 가격 대비 ATR 비율의 10%를 가산합니다.
        dynamic_slippage_pct = base_slippage + (current_atr / current_close) * 0.10
        
        # 1. 매수 신호 발생 시
        if signal == 1 and position == 0:
            # 변동성에 비례하여 의도한 가격보다 '더 비싸게' 매수 체결
            exec_price = current_close * (1 + dynamic_slippage_pct)
            
            # 수수료 차감 후 진입
            entry_value = seed * (1 - fee_rate)
            position_size = entry_value / exec_price
            
            buy_price = exec_price
            seed = 0.0
            position = 1
            
            print(f&quot;[{df.index[i]}] 매수 체결 - 기준가: {current_close:.2f} | 실제체결가(동적슬리피지 {dynamic_slippage_pct*100:.3f}% 반영): {exec_price:.2f} | 수수료: {entry_value * fee_rate:.2f}원&quot;)
            
        # 2. 매도 신호 발생 시
        elif signal == -1 and position == 1:
            # 변동성에 비례하여 의도한 가격보다 '더 싸게' 매도 체결
            exec_price = current_close * (1 - dynamic_slippage_pct)
            
            # 총 판매 대금에서 매도 수수료 차감
            gross_sale = position_size * exec_price
            net_sale = gross_sale * (1 - fee_rate)
            
            seed = net_sale
            position = 0
            trade_return = (exec_price - buy_price) / buy_price * 100
            
            print(f&quot;[{df.index[i]}] 매도 체결 - 기준가: {current_close:.2f} | 실제체결가(동적슬리피지 {dynamic_slippage_pct*100:.3f}% 반영): {exec_price:.2f} | 거래수익률: {trade_return:+.2f}%&quot;)
            
        # 현재 자산 평가액 기록
        current_val = seed if position == 0 else (position_size * current_close)
        balance_history.append(current_val)
        
    df['portfolio_value'] = balance_history
    return df
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 실전 봇의 슬리피지 방어 전략&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 거래 마찰 비용을 방치하면 아무리 좋은 전략도 파산에 이를 수 있습니다. 실물 트레이딩 봇 개발 시 슬리피지를 억제하기 위해 사용해야 하는 실전 엔지니어링 전략을 소개합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;지정가 주문(Limit Order) 적극 활용&lt;/b&gt;: 무조건적인 시장가 진입 대신, 현재 매도 1호가에 지정가로 주문을 넣고 일정 시간(예: 10초) 동안 체결을 기다린 뒤 안 되면 취소 후 다시 가격을 보정하는 '지정가 추적 알고리즘'을 도입합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;호가창 두께 검사(Order Book Depth Check)&lt;/b&gt;: 주문을 넣기 전에 거래소 API로 현재 매매 대상 코인의 호가 잔량을 조회하여, 내 주문 수량이 호가창 1호가의 잔량을 초과하지 않는지 체크합니다. 만약 주문 수량이 1호가 잔량보다 크다면 주문을 잘게 분할하여 집행(TWAP, 시간가중분할주문)합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;거래 주기 조절&lt;/b&gt;: 마찰 비용은 거래 횟수가 많아질수록 기하급수적으로 복리 누적됩니다. 1분선이나 5분선 단위의 초단기 단타 매매는 마찰 비용 감당이 극히 어렵습니다. 가급적 1시간선, 4시간선, 일선 등 거래 빈도가 낮고 파동이 큰 타임프레임을 타겟팅하여 수수료의 상대적 비중을 최소화해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚠️ 리스크 경고: 무거래 상태에서의 고정 수수료와 펀딩피&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;지속적인 고정 수수료&lt;/b&gt;: API 연결에 따르는 호가 조회 자체는 무료이지만, 레버리지 선물 거래를 하는 경우 포지션을 들고만 있어도 8시간마다 발생하는 **'펀딩비(Funding Fee)'**가 아웃바운드 비용으로 계좌를 좀먹을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유동성 고갈 시기의 스프레드 발산&lt;/b&gt;: 거래량이 급감하는 심야 시간대나 미 연준 금리 발표 등 대형 매크로 지표 발표 직후에는 호가창에 물량이 일시적으로 사라지면서 매수-매도 스프레드가 평소의 10배 이상 벌어집니다. 이 구간에 무지성으로 시장가 주문을 쏘면 엄청난 슬리피지 폭탄을 맞고 계좌가 복구 불가능한 타격을 입을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;에필로그: 다음 단계는 '텔레그램 양방향 긴급제어!'&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백테스팅 결과에 슬리피지와 수수료라는 리얼한 필터를 씌우고 나니, 드디어 실전에서 견딜 수 있는 진짜 강인한 자동매매 전략이 모습을 갖추기 시작했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다음 편 예고 체크리스트&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] 24시간 도는 봇의 상황을 내 손안의 스마트폰으로 실시간 브리핑 받기&lt;/li&gt;
&lt;li&gt;[ ] 텔레그램 메시지 전송(단방향)을 넘어, 스마트폰에서 봇에 명령을 내리는 양방향 제어 모듈 조립&lt;/li&gt;
&lt;li&gt;[ ] 시장 급변 시 즉각 모든 포지션을 청산하고 봇을 일시 중단시키는 긴급 비상 킬스위치(Kill-Switch) 코딩&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편에서는 클라우드에서 외롭게 일하고 있는 내 봇에게 무선 조종기를 달아주는 시간, **'텔레그램 양방향 API 연동과 긴급 킬스위치 제어 모듈 조립'**으로 찾아오겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;마찰 비용의 벽을 넘어야 진짜 1%의 트레이더가 될 수 있습니다. 오늘 내용이 유익했다면 공유와 구독 부탁드립니다. 감사합니다!&lt;/b&gt;&lt;/p&gt;</description>
      <author>myaistories</author>
      <guid isPermaLink="true">https://myaistories.tistory.com/25</guid>
      <comments>https://myaistories.tistory.com/25#entry25comment</comments>
      <pubDate>Wed, 27 May 2026 06:54:02 +0900</pubDate>
    </item>
    <item>
      <title>[시즌 3 - 3화] &amp;quot;단 1원도 잃지 않는 연습&amp;quot; 거래소 API 테스트넷(Testnet) 모의투자 연동 가이드</title>
      <link>https://myaistories.tistory.com/24</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 지난 연재에서 우리는 가상 서버(AWS 라이트세일) 환경에 봇을 안착시키고, 프로세스가 불시에 종료되어도 직전의 상태와 주문 내역을 완벽히 복원해낼 수 있는 'SQLite 데이터베이스 연동 모듈'까지 완성했습니다. 이제 봇에게 영혼(서버)과 기억(DB)이 생겼으니, 드디어 실제 거래를 태워볼 시간입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아무리 완벽하게 설계한 자동매매 봇이라도, 첫날부터 실전 계좌의 진짜 내 돈을 걸어 실행시키는 것은 극히 위험합니다. API 호출 인자 값 하나를 잘못 넣거나, 매수량 계산 공식의 소수점 처리 오차로 인해 순식간에 주문이 거부되거나 엉뚱한 주문이 들어갈 수 있기 때문입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&quot;내 진짜 돈은 지키면서, 실시간으로 요동치는 코인 호가창에 맞춰 매수/매도 주문이 제대로 나가고 체결되는지 테스트해볼 순 없을까?&quot;&lt;/i&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 거래소들이 무료로 제공하는 연습용 서버가 바로 &lt;b&gt;'테스트넷(Testnet)'&lt;/b&gt; 또는 **'샌드박스(Sandbox)'**입니다. 오늘 연재에서는 글로벌 표준 금융 통신 라이브러리인 &lt;b&gt;CCXT&lt;/b&gt;를 활용하여, 단 1원의 리스크도 지지 않으면서 실시간 가상 화폐 주문을 처리하는 모의투자 시스템을 구축해 보겠습니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 거래소 테스트넷(Testnet)과 CCXT 샌드박스 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 메이저 거래소(Binance, Bybit 등)는 개발자를 위해 실제 시장 가격과 유사하게 움직이면서 무상으로 가상의 테스트 자금(USDT 등)을 리필해 주는 테스트용 API 환경을 별도로 개방하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 가이드에서는 가상화폐 자동매매 개발에 가장 널리 쓰이는 멀티-거래소 표준 라이브러리 **CCXT(CryptoCurrency eXchange Trading)**를 활용하겠습니다. CCXT를 사용하면 코드 한 줄만으로 거래소 연결을 실전 모드에서 모의 테스트 모드로 간단히 전환할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Bybit/Binance 테스트넷 API 키 발급 가이드&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;거래소 테스트넷 사이트&lt;/b&gt; (예: Bybit Testnet 또는 Binance Testnet)에 접속하여 별도의 회원가입을 진행합니다. (실제 거래소 계정과 분리되어 있습니다.)&lt;/li&gt;
&lt;li&gt;가입 후 대시보드에서 **[API Key] -&amp;gt; [Create New Key]**를 선택합니다.&lt;/li&gt;
&lt;li&gt;API 권한 설정에서 &lt;code&gt;Active Order&lt;/code&gt;(주문 생성) 및 &lt;code&gt;Account Read&lt;/code&gt;(잔고 조회) 권한을 부여합니다.&lt;/li&gt;
&lt;li&gt;발급된 &lt;b&gt;API Key&lt;/b&gt;와 &lt;b&gt;Secret Key&lt;/b&gt;를 복사하여 준비해 둡니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. CCXT를 활용한 테스트넷 연동 및 잔고 조회&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 파이썬 코드를 작성하여 가상 거래소에 접속하고, 내 테스트 계좌에 충전되어 있는 가상의 달러(USDT) 자산을 읽어오는 모듈을 조립해 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import ccxt
import json

def connect_testnet(config_path=&quot;token.json&quot;):
    &quot;&quot;&quot;
    token.json 파일에서 API Key를 읽어와 
    테스트넷(Sandbox Mode) 거래소 인스턴스를 생성합니다.
    &quot;&quot;&quot;
    # API 키 파일 로드
    with open(config_path, &quot;r&quot;) as f:
        config = json.load(f)
        
    # Bybit 거래소를 기준으로 설정 (필요시 binance 등으로 변경 가능)
    exchange = ccxt.bybit({
        'apiKey': config['TESTNET_API_KEY'],
        'secret': config['TESTNET_SECRET_KEY'],
        'enableRateLimit': True, # API 호출 제한을 CCXT 내부적으로 준수하도록 함
    })
    
    # [CRITICAL] 샌드박스 모드를 True로 설정해야 실제 돈이 나가지 않고 테스트넷으로 붙습니다!
    exchange.set_sandbox_mode(True)
    
    return exchange

if __name__ == &quot;__main__&quot;:
    try:
        # 가상 거래소 인스턴스 획득
        exchange = connect_testnet()
        
        # 지갑 잔고 조회 (통합 마진/현물 지갑 정보)
        balance = exchange.fetch_balance()
        
        # USDT 잔고 정보만 파싱하여 출력
        usdt_balance = balance.get('USDT', {})
        print(&quot;==== 테스트넷 연결 성공 ====&quot;)
        print(f&quot;보유한 가상 USDT 총액: {usdt_balance.get('total', 0.0)} USDT&quot;)
        print(f&quot;주문 가능 가상 USDT: {usdt_balance.get('free', 0.0)} USDT&quot;)
        
    except Exception as e:
        print(f&quot;테스트넷 연결 중 오류가 발생했습니다: {e}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 테스트넷 기반 모의 매수/매도 및 DB 기록 통합&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 단순히 가격을 조회하는 단계를 넘어, 특정 가격에 기계적으로 주문을 밀어 넣고 체결 결과를 받아오는 함수를 작성해 봅시다. 이때, 주문이 무사히 성공하면 지난 연재에서 만들었던 &lt;code&gt;TradeDatabase&lt;/code&gt;와 결합하여 데이터베이스에 주문 로그가 자동으로 기록되도록 파이프라인을 연결합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import time
from datetime import datetime
# 이전 2화에서 만든 데이터베이스 모듈을 가져옵니다.
from database_module import TradeDatabase 

def execute_mock_trade(exchange, db, symbol, side, amount, price=None):
    &quot;&quot;&quot;
    테스트넷에 매수/매도 주문을 접수하고 체결 내역을 데이터베이스에 영구 보존합니다.
    - price가 None이면 시장가(Market) 주문, 값이 있으면 지정가(Limit) 주문을 실행합니다.
    &quot;&quot;&quot;
    try:
        # [고급 기능] 거래소 규격에 맞는 가격/수량 소수점 정밀도 자동 포맷팅
        # 거래소마다 코인의 소수점 자릿수 제약이나 최소 주문단위가 다릅니다.
        # 소수점 오차가 있으면 거래소 엔진이 주문을 즉각 Reject하므로 아래 변환은 필수입니다.
        exchange.load_markets()
        
        formatted_amount = float(exchange.amount_to_precision(symbol, amount))
        formatted_price = None
        if price is not None:
            formatted_price = float(exchange.price_to_precision(symbol, price))
            
        print(f&quot;\n[주문 실행] {symbol} {side} {formatted_amount}개 전송 중... (요청수량: {amount})&quot;)
        
        # 1. 시장가 주문과 지정가 주문을 구분하여 요청
        if price is None:
            # 시장가 주문
            order = exchange.create_order(
                symbol=symbol,
                type='market',
                side=side.lower(), # 'buy' or 'sell'
                amount=formatted_amount
            )
        else:
            # 지정가 주문
            order = exchange.create_order(
                symbol=symbol,
                type='limit',
                side=side.lower(),
                amount=formatted_amount,
                price=formatted_price
            )
            
        print(f&quot;[주문 완료] 거래소 주문 접수 성공 - ID: {order['id']}&quot;)
        
        # 2. 체결 대기 및 상세 결과 파싱
        # 테스트넷은 실제 체결 속도와 차이가 있을 수 있으므로 API에서 체결 세부정보를 다시 조회합니다.
        time.sleep(1) # 잠시 후 체결 상태 업데이트 확인
        order_info = exchange.fetch_order(order['id'], symbol)
        
        # 체결 가격, 수량, 수수료 추출
        filled_price = order_info.get('price', formatted_price) if order_info.get('price') else formatted_price
        filled_amount = order_info.get('filled', formatted_amount)
        
        # 수수료 계산 (수수료 정보가 누락되었을 경우 기본 요율 0.1%로 가산)
        fees = order_info.get('fee', {})
        fee_cost = fees.get('cost', filled_amount * filled_price * 0.001) 
        
        # 3. SQLite 데이터베이스에 영구 보존
        db.record_trade(
            symbol=symbol,
            side=side.upper(),
            price=filled_price,
            amount=filled_amount,
            fee=fee_cost,
            order_id=order['id']
        )
        
        # 4. 실시간 정산 기록을 위한 자산 스냅샷 자동 반영
        balance = exchange.fetch_balance()
        total_balance = balance.get('total', {}).get('USDT', 0.0)
        cash_balance = balance.get('free', {}).get('USDT', 0.0)
        db.record_balance(total_balance=total_balance, cash=cash_balance)
        
        return order_info
        
    except Exception as e:
        print(f&quot;[주문 오류] 주문 실행 또는 DB 기록 중 문제가 생겼습니다: {e}&quot;)
        return None

# 실행 예제
if __name__ == &quot;__main__&quot;:
    exchange = connect_testnet()
    db = TradeDatabase(&quot;trade_bot.db&quot;)
    
    # 비트코인(BTC/USDT) 가상 시장가 매수 테스트
    # 실제 돈이 아님으로 안심하고 0.001 BTC를 시장가 매수해 봅니다.
    order_result = execute_mock_trade(
        exchange=exchange,
        db=db,
        symbol=&quot;BTC/USDT&quot;,
        side=&quot;BUY&quot;,
        amount=0.001
    )
    
    db.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚠️ 리스크 경고: 테스트넷의 한계와 실전 매매의 함정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트넷은 안전한 훈련소 역할을 하지만, 실제 전장(Live Market)과는 결정적인 차이가 존재합니다. 이를 간과하면 실전 투입 후 당황스러운 경제적 손실을 입을 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;체결 괴리 (Thin Order Book)&lt;/b&gt;: 테스트넷은 거래 참여자가 거의 없거나, 가상의 유동성 공급 프로그램(Market Maker)만 작동하므로 호가창이 매우 얇습니다. 따라서 수천만 원 상당의 큰 수량을 테스트넷에 던지면, 실제 시장보다 훨씬 극단적으로 불리한 가격에 체결(Slippage)되거나 아예 주문이 거부될 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 가용성 이슈&lt;/b&gt;: 거래소들의 테스트넷 서버는 라이브 서버보다 컴퓨팅 자원 배정이 낮고 불안정하여 수시로 일시 정지되거나 리셋됩니다. API 호출 실패 에러가 더 빈번히 나므로 이를 봇 오작동으로 오해하지 않도록 예외 코드 블록을 세심히 감싸 두어야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주문 한계 금액 제약&lt;/b&gt;: 비트코인은 최소 주문량이 &lt;code&gt;0.0001 BTC&lt;/code&gt; 등 매우 작지만, 일부 테스트넷에서는 테스트 편의를 위해 최소 수량 단위를 대폭 높여두어 소액 테스트 코드가 막힐 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;에필로그: 다음 단계는 '실전 거래 마찰비용 모델링!'&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 가상 자금을 통해 24시간 실시간 가격을 분석하고, 거래소 API에 합법적으로 매수/매도 전령을 보내 거래 기록을 DB에 쌓는 '모의 모의투자 실습' 과정이 정식 활성화되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다음 편 예고 체크리스트&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] 백테스팅의 이상과 실전 매매의 슬픈 현실: 수수료와 슬리피지(Slippage)&lt;/li&gt;
&lt;li&gt;[ ] 주문을 던지는 찰나의 시간 차(API 레이턴시)가 만들어내는 가격 괴리 계산&lt;/li&gt;
&lt;li&gt;[ ] 봇의 누적 수익률 계산 시 주문 체결 딜레이를 반영하는 정밀 보정 공식 코딩&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 시간에는 샌드박스 테스트에서 미처 보지 못했던 거래 비용의 진짜 적들, 즉 **'슬리피지(Slippage)와 네트워크 지연 등 거래 마찰 비용 모델링'**을 어떻게 코드로 설계하고 백테스터에 반영할지 파헤쳐 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오늘도 유익하게 읽으셨다면 구독과 좋아요, 그리고 질문이 있다면 언제든 댓글로 남겨주세요. 감사합니다!&lt;/b&gt;&lt;/p&gt;</description>
      <author>myaistories</author>
      <guid isPermaLink="true">https://myaistories.tistory.com/24</guid>
      <comments>https://myaistories.tistory.com/24#entry24comment</comments>
      <pubDate>Wed, 27 May 2026 06:53:35 +0900</pubDate>
    </item>
    <item>
      <title>[시즌 3 - 2화] &amp;quot;재시작해도 기억을 잃지 않는 봇&amp;quot; SQLite 데이터베이스 설계와 트레이딩 로그 자동 기록 모듈 개발</title>
      <link>https://myaistories.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 지난 시간에 가상 서버인 AWS 라이트세일 환경에서 24시간 작동하는 리눅스 터미널 구동 환경을 성공적으로 개설했습니다. 이제 봇이 무한 구동 상태로 밤낮없이 돌아가기 시작했으니, 본격적으로 그 안에서 발생하는 데이터들을 영구적으로 보존할 장치가 필요합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&quot;어제 산 비트코인이 지금 봇 메모리에 들어있는데, 만약 서버가 재부팅되면 봇은 자기가 뭘 샀는지 기억할 수 있을까?&quot;&lt;/i&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 시즌처럼 시드 배분이나 매매 로그를 단순한 텍스트 파일(&lt;code&gt;.txt&lt;/code&gt;)이나 &lt;code&gt;.csv&lt;/code&gt; 파일에 한 줄씩 기록하는 방식은 한계가 명확합니다. 프로그램이 갑자기 정전이나 강제 종료를 맞았을 때, 혹은 동시에 여러 프로세스가 로그를 쓸 때 파일이 깨져버리거나(File Corruption) 엉키기 쉽기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 24시간 도는 실전 트레이딩 봇의 뇌 역할을 해줄 아주 가볍고 견고한 내장형 관계형 데이터베이스인 &lt;b&gt;SQLite&lt;/b&gt;를 봇에 심어줄 시간입니다. 오늘 연재에서는 재시작 후에도 봇이 자기 자산과 포지션을 완벽히 복원해내도록 돕는 데이터베이스 스키마 설계와 자동 연동 모듈을 완성해 보겠습니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 봇을 위한 SQLite 경량 데이터베이스 테이블 스키마 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLite는 별도의 복잡한 데이터베이스 서버 설치 필요 없이, 파이썬 기본 라이브러리에 내장되어 있어 파일 하나(&lt;code&gt;trade_bot.db&lt;/code&gt;)로 모든 것을 보관해주는 환상적인 크립토 특화 DB 엔진입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 설계할 트레이딩 테이블은 크게 2개입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;trades&lt;/code&gt; 테이블&lt;/b&gt;: 매수/매도의 실제 거래 체결 내역 기록&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;balance_history&lt;/code&gt; 테이블&lt;/b&gt;: 매일 또는 매 매매 시점의 계좌 자산 평가액 추이 보존 (수익률 그래프용)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- 1. 거래 이력 보관 테이블
CREATE TABLE IF NOT EXISTS trades (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
    symbol TEXT NOT NULL,
    side TEXT NOT NULL,       -- 'BUY' or 'SELL'
    price REAL NOT NULL,
    amount REAL NOT NULL,
    fee REAL NOT NULL,
    order_id TEXT UNIQUE      -- 거래소 고유 주문 ID
);

-- 2. 자산 변동성 모니터링 테이블
CREATE TABLE IF NOT EXISTS balance_history (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
    total_balance REAL NOT NULL, -- 총 평가금액 (현금 + 보유코인 평가액)
    cash REAL NOT NULL           -- 보유 중인 순수 현금 자산
);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 파이썬 SQLite 데이터베이스 자동화 모듈 연동&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 파이썬 코드 내부에서 위 테이블을 생성하고, 거래가 체결되거나 정산이 끝났을 때 데이터베이스에 실시간으로 로그를 주입(Insert)하는 제어용 데이터베이스 클래스를 조립해 봅니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import sqlite3
import os
from datetime import datetime

class TradeDatabase:
    def __init__(self, db_path=&quot;trade_bot.db&quot;):
        self.db_path = db_path
        # 데이터베이스 잠금(Lock) 충돌 방지를 위한 timeout 설정 필수
        self.conn = sqlite3.connect(self.db_path, timeout=30.0)
        # [고급 성능 최적화] WAL(Write-Ahead Logging) 모드 활성화
        # 읽기/쓰기를 분리하여 동시성 성능을 높이고 database locked 오류 발생을 극소화합니다.
        self.conn.execute(&quot;PRAGMA journal_mode=WAL;&quot;)
        self.create_tables()

    def create_tables(self):
        &quot;&quot;&quot;봇 구동에 필수적인 테이블 자동 생성&quot;&quot;&quot;
        cursor = self.conn.cursor()
        
        # 거래 기록 테이블 생성
        cursor.execute(&quot;&quot;&quot;
            CREATE TABLE IF NOT EXISTS trades (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp TEXT NOT NULL,
                symbol TEXT NOT NULL,
                side TEXT NOT NULL,
                price REAL NOT NULL,
                amount REAL NOT NULL,
                fee REAL NOT NULL,
                order_id TEXT UNIQUE
            )
        &quot;&quot;&quot;)
        
        # 자산 변동 리포트용 테이블 생성
        cursor.execute(&quot;&quot;&quot;
            CREATE TABLE IF NOT EXISTS balance_history (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp TEXT NOT NULL,
                total_balance REAL NOT NULL,
                cash REAL NOT NULL
            )
        &quot;&quot;&quot;)
        self.conn.commit()

    def record_trade(self, symbol, side, price, amount, fee, order_id):
        &quot;&quot;&quot;매매 신호 체결 정보 데이터베이스 입력&quot;&quot;&quot;
        try:
            cursor = self.conn.cursor()
            now = datetime.now().strftime(&quot;%Y-%m-%d %H:%M:%S&quot;)
            cursor.execute(&quot;&quot;&quot;
                INSERT INTO trades (timestamp, symbol, side, price, amount, fee, order_id)
                VALUES (?, ?, ?, ?, ?, ?, ?)
            &quot;&quot;&quot;, (now, symbol, side, price, amount, fee, order_id))
            self.conn.commit()
            print(f&quot;[DB] {side} 기록 성공 - 주문 ID: {order_id}&quot;)
        except sqlite3.IntegrityError:
            print(f&quot;[DB 경고] 이미 존재하는 주문 ID 입니다: {order_id}&quot;)
        except Exception as e:
            print(f&quot;[DB 오류] 거래 기록 실패: {e}&quot;)

    def record_balance(self, total_balance, cash):
        &quot;&quot;&quot;일일 총자산 규모 트래킹 데이터 입력&quot;&quot;&quot;
        try:
            cursor = self.conn.cursor()
            now = datetime.now().strftime(&quot;%Y-%m-%d %H:%M:%S&quot;)
            cursor.execute(&quot;&quot;&quot;
                INSERT INTO balance_history (timestamp, total_balance, cash)
                VALUES (?, ?, ?)
            &quot;&quot;&quot;, (now, total_balance, cash))
            self.conn.commit()
            print(f&quot;[DB] 자산 스냅샷 보존 완료 - 총액: {total_balance}원&quot;)
        except Exception as e:
            print(f&quot;[DB 오류] 자산 스냅샷 실패: {e}&quot;)

    def close(self):
        self.conn.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 예외 상황 처리: 자산 상태 복구(State Restore) 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모듈의 진정한 진가는 **시스템의 정상 복구 능력(Resiliency)**에 있습니다. 봇이 돌연 죽었다가 다시 살아날 때, 데이터베이스에서 가장 최근 기록된 보유 포지션 상태를 긁어오면 복잡한 외부 연동 없이도 즉각 자가 뇌이식이 가능합니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;def restore_bot_state(db_path=&quot;trade_bot.db&quot;):
    &quot;&quot;&quot;
    가장 최근에 기록된 마지막 거래 정보를 가져와 현재 보유 중인 자산 유무 진단.
    가장 최신 거래가 'BUY'였다면 현재 봇은 포지션을 보유 중인 상태로 인식 복구.
    &quot;&quot;&quot;
    if not os.path.exists(db_path):
        return {&quot;position&quot;: &quot;NONE&quot;, &quot;last_price&quot;: 0.0}

    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()
    cursor.execute(&quot;SELECT side, price, amount FROM trades ORDER BY id DESC LIMIT 1&quot;)
    row = cursor.fetchone()
    conn.close()

    if row:
        last_side, last_price, last_amount = row
        if last_side == &quot;BUY&quot;:
            return {&quot;position&quot;: &quot;LONG&quot;, &quot;last_price&quot;: last_price, &quot;amount&quot;: last_amount}
    
    return {&quot;position&quot;: &quot;NONE&quot;, &quot;last_price&quot;: 0.0, &quot;amount&quot;: 0.0}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚠️ 리스크 경고: SQLite 동시 쓰기 잠금(Database Locked) 해결법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLite는 기본적으로 파일 기반의 가벼운 DB이기 때문에, &lt;b&gt;동시에 여러 작업이 쓰기(Write/Update)를 시도하면 &lt;code&gt;sqlite3.OperationalError: database is locked&lt;/code&gt; 오류가 발생&lt;/b&gt;하며 봇의 기록이 멈추거나 튕길 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;해결책 1 (강력 권장): WAL(Write-Ahead Logging) 모드 활성화&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;conn.execute(&quot;PRAGMA journal_mode=WAL;&quot;)&lt;/code&gt; 구문을 통해 WAL 모드를 켜면, 쓰기 작업이 진행 중이어도 읽기 작업을 대기 없이 즉시 수행할 수 있습니다. 대부분의 락(Lock) 충돌 문제를 소스 수준에서 예방할 수 있는 최고 효율의 옵션입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결책 2&lt;/b&gt;: 데이터베이스 인스턴스를 커넥션 생성 시 &lt;code&gt;sqlite3.connect('trade_bot.db', timeout=30.0)&lt;/code&gt;와 같이 &lt;code&gt;timeout&lt;/code&gt; 대기 시간을 30초 이상 넉넉히 설정하여, 락(Lock)이 해제될 때까지 프로세스가 지치지 않고 기다리게 방어합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결책 3&lt;/b&gt;: 멀티스레딩 환경에서 봇을 돌린다면 쓰기 스레드는 오직 단 하나만 담당하도록 &lt;b&gt;큐(Queue) 패턴&lt;/b&gt;을 접목하거나, 데이터 쓰기 작업 직후 반드시 &lt;code&gt;conn.commit()&lt;/code&gt;을 통해 트랜잭션을 짧게 가져가 락을 최대한 빠르게 반환하는 습관이 중요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;에필로그: 다음 단계는 '거래소 테스트넷 모의투자!'&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기억 상실에 빠지지 않는 불사조 같은 자동매매 봇의 튼튼한 메모리 보관함(DB)이 완성되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다음 편 예고 체크리스트&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] 24시간 실전 모의 자금 세팅 및 계정 연결&lt;/li&gt;
&lt;li&gt;[ ] 가상의 API 키를 활용한 테스트넷(Testnet) 연동&lt;/li&gt;
&lt;li&gt;[ ] 실시간 비트코인 호가 창을 분석해 가상 자금으로 주문을 넣고 체결하는 실전 모의 트레이딩 코어 코딩&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편에서는 내 돈을 한 푼도 잃지 않고 실제 시장에서 거래가 어떻게 주문 체결되는지 실시간 테스트할 수 있는 **'거래소 테스트넷(Testnet) 모의투자 연동과 모의 체결 테스트'**를 설계해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오늘도 유익하셨다면 구독과 따뜻한 댓글 부탁드립니다. 감사합니다!&lt;/b&gt;&lt;/p&gt;</description>
      <author>myaistories</author>
      <guid isPermaLink="true">https://myaistories.tistory.com/23</guid>
      <comments>https://myaistories.tistory.com/23#entry23comment</comments>
      <pubDate>Wed, 27 May 2026 06:53:06 +0900</pubDate>
    </item>
    <item>
      <title>[시즌 3 - 1화] &amp;quot;24시간 꺼지지 않는 봇 만들기&amp;quot; AWS 라이트세일(LightSail) 클라우드 배포와 백그라운드 구동 가이드</title>
      <link>https://myaistories.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! AI와 함께 나만의 트레이딩 전략을 세우고, 3개년 백테스팅 검증을 거쳐 리스크 관리 장치(Kelly 공식, ATR 손절선)까지 탑재한 지난 여정을 모두 기억하시나요? 백테스팅 결과를 보며 자신감에 차 실전 매매를 돌리려다 보니, 아주 현실적인 인프라 장벽에 부딪히게 되었습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&quot;설마 자동매매 봇을 돌리려고 내 개인 노트북과 데스크톱을 24시간 내내 켜두어야 하나?&quot;&lt;/i&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집에 있는 PC를 24시간 구동하는 것은 전기세 문제뿐 아니라, 운영체제 자동 업데이트로 인한 재부팅, 인터넷 일시 끊김, 하드웨어 수명 등 수많은 위험 요소를 안겨줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 실전 자동 매매의 첫걸음은 내 개인 PC의 전원 공급 상태와 무관하게 전 세계에서 가장 안정적인 인프라 위에서 구동되는 **'클라우드 가상 서버(VPS)'**에 봇을 이주시키는 것입니다. 이번 시즌 3에서는 24시간 돌아가는 실전 봇의 안전한 배포와 실시간 긴급 제어 인프라 구축을 시작합니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 최저비용으로 시작하는 AWS 라이트세일 가상 서버 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 서버를 대여해주는 서비스(클라우드) 중 입문자에게 가장 직관적이고 요금이 정액제로 투명한 **AWS 라이트세일(LightSail)**을 선택합니다. 한 달에 커피 한 잔 가격 수준($3.5~$5)으로 24시간 리눅스 서버를 대여할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  가상 서버(Instance) 사양 설정 가이드&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;AWS Console 로그인&lt;/b&gt; 후 LightSail 서비스 페이지로 이동합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[인스턴스 생성]&lt;/b&gt; 버튼을 누릅니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;플랫폼 선택&lt;/b&gt;: &lt;code&gt;Linux/Unix&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;블루프린트 선택&lt;/b&gt;: &lt;code&gt;OS 전용&lt;/code&gt; -&amp;gt; &lt;code&gt;Ubuntu 22.04 LTS&lt;/code&gt; (가장 널리 쓰이며 정보가 많습니다)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인스턴스 플랜 선택&lt;/b&gt;: CPU 1vCPU, RAM 512MB 또는 1GB 플랜 (가장 저렴한 플랜으로도 파이썬 자동매매 봇 구동에는 차고 넘칩니다)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인스턴스 이름 입력&lt;/b&gt; 후 생성 버튼을 누르면 약 1분 만에 가상 서버가 활성화됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 보안 그룹 설정 및 터미널 원격 접속&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스 생성이 완료되면 고유한 **공인 IP(Static IP)**가 부여됩니다. 외부에서 이 서버에 명령어를 입력하려면 원격 터미널 프로토콜인 SSH(포트 22)가 활성화되어 있어야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  고정 IP 및 방화벽 설정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;고정 IP 생성&lt;/b&gt;: 라이트세일 네트워킹 탭에서 [고정 IP 연결]을 실행하여 서버가 재부팅되어도 IP가 변경되지 않도록 고정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;포트 개방&lt;/b&gt;: 기본적으로 SSH(22포트)는 열려 있습니다. 이후 텔레그램이나 디스코드 웹훅 연동을 위해 아웃바운드(Outbound) 인터넷 포트는 모두 자유롭게 열려 있음을 확인해 둡니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 리눅스 서버 파이썬 환경 구축 및 코드 이주&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 내부(Ubuntu)로 접속하기 위해 라이트세일 브라우저 터미널을 열거나 원격 SSH 툴을 사용합니다. 이제 아무것도 깔려있지 않은 빈 OS 위에 파이썬 개발 환경을 조립해 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# Ubuntu 시스템 패키지 최신화
sudo apt-get update &amp;amp;&amp;amp; sudo apt-get upgrade -y

# 파이썬 및 패키지 매니저(pip) 설치
sudo apt-get install python3 python3-pip -y

# 기존 자동매매 봇 구동에 필요한 라이브러리 일괄 설치
pip3 install ccxt pandas requests telegram
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되면, SFTP 프로그램이나 &lt;code&gt;git clone&lt;/code&gt; 명령어를 통해 로컬에서 개발한 &lt;code&gt;main.py&lt;/code&gt; 파일과 &lt;code&gt;token.json&lt;/code&gt; 등 필수 설정 자산을 서버 내부 폴더(&lt;code&gt;/home/ubuntu/bot/&lt;/code&gt;)로 복사합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. nohup을 활용한 24시간 백그라운드 구동과 로그 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 서버 터미널에서 &lt;code&gt;python3 main.py&lt;/code&gt;를 실행하면 봇이 작동하기 시작합니다. 하지만, &lt;b&gt;원격 터미널 창을 닫아버리는 순간 터미널 세션이 종료되면서 봇도 강제로 종료&lt;/b&gt;됩니다. 터미널 창을 꺼도 24시간 동안 백그라운드에서 코드가 홀로 실행되도록 제어해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 백그라운드 실행 명령어: nohup&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스 표준 명령어인 &lt;code&gt;nohup&lt;/code&gt;과 백그라운드 지시자 &lt;code&gt;&amp;amp;&lt;/code&gt;를 결합하여 실행합니다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;# 터미널이 끊겨도 백그라운드에서 파이썬 스크립트를 24시간 계속 구동
# stdout/stderr 출력 로그는 realtime_trade.log 파일에 즉시 기록
nohup python3 -u main.py &amp;gt; realtime_trade.log 2&amp;gt;&amp;amp;1 &amp;amp;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;-u&lt;/code&gt;: 파이썬 출력 버퍼링을 비활성화하여 로그 파일에 출력값이 지연 없이 실시간으로 즉시 기록되도록 강제합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt;: 에러 메시지(stderr)와 일반 출력(stdout)을 한곳에 합쳐서 파일로 받습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;amp;&lt;/code&gt;: 프로세스를 완전히 백그라운드로 밀어 넣습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  고급 팁: 디스크 풀(Disk Full) 방지를 위한 파이썬 롤링 로그 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 &lt;code&gt;print&lt;/code&gt; 명령어로 로그를 무한정 쌓아두면 몇 달 뒤 가상 서버의 디스크 용량(최저 플랜은 20GB 내외)이 가득 차 서버가 다운되는 사태가 발생합니다. 파이썬의 내장 &lt;code&gt;logging&lt;/code&gt; 모듈과 &lt;code&gt;RotatingFileHandler&lt;/code&gt;를 사용해 파일 크기를 제한하는 롤링 로그를 설정하는 것이 프로덕션의 기본입니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import logging
from logging.handlers import RotatingFileHandler

# 로거 생성
logger = logging.getLogger(&quot;TradingBot&quot;)
logger.setLevel(logging.INFO)

# 롤링 파일 핸들러 설정: 파일 하나당 최대 10MB, 백업 파일은 최대 5개 유지
handler = RotatingFileHandler(
    &quot;realtime_trade.log&quot;, 
    maxBytes=10 * 1024 * 1024, # 10MB
    backupCount=5,
    encoding=&quot;utf-8&quot;
)

# 로그 포맷 정의
formatter = logging.Formatter('[%(asctime)s] %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

# 실전 사용
logger.info(&quot;자동매매 봇 백그라운드 구동이 개시되었습니다.&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  실시간 작동 로그 점검&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 작동 중 봇이 잘 돌고 있는지 로그 파일을 실시간으로 모니터링하려면 &lt;code&gt;tail&lt;/code&gt; 명령어를 활용합니다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;# 로그 파일의 마지막 20줄을 출력하고, 새로운 출력이 누적될 때마다 화면에 실시간 갱신
tail -f realtime_trade.log
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚠️ 리스크 경고: 클라우드 구동 시 쿼터와 오버헤드 주의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 서버는 로컬 컴퓨터와 네트워크 가동 환경이 완전히 다릅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;시간 동기화 오차&lt;/b&gt;: 클라우드 가상 서버의 OS 시간(UTC)이 실제 한국 시간(KST)이나 거래소 서버 시간과 미세하게 일치하지 않으면 API 요청 시 타임아웃 서명 에러가 터집니다. Ubuntu 타임존을 한국 시간으로 강제 동기화하는 설정을 필수로 해두어야 합니다.
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;sudo timedatectl set-timezone Asia/Seoul
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 사양에 따른 버퍼 오버플로우&lt;/b&gt;: 512MB RAM 요금제를 선택한 경우, 봇 내부에서 대량의 판다스 백테스팅 프레임을 상시 메모리에 띄워 계산하면 메모리 부족(OOM Killer)으로 프로세스가 OS에 의해 강제 살해당할 수 있습니다. 경량화된 실행 구조가 필수적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;에필로그: 다음 단계는 'SQLite 데이터베이스 연동!'&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 봇이 이제 클라우드 인프라 위에서 24시간 밤낮없이 호가를 조회하고 거래 신호를 포착할 준비를 끝마쳤습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다음 편 예고 체크리스트&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] 파일 데이터 저장 방식의 한계를 넘는 SQLite 가벼운 DB 설계&lt;/li&gt;
&lt;li&gt;[ ] 봇이 체결한 실시간 주문 내역을 DB에 적재하는 SQL 쿼리 연동&lt;/li&gt;
&lt;li&gt;[ ] 재부팅 후에도 기존 매매 상태(시드 배분 상태, 잔고) 복원 모듈 조립&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 시간에는 리눅스 서버가 돌발 재부팅되더라도 데이터가 증발하지 않고 내 봇의 기억을 영구 보존해줄 &lt;b&gt;'SQLite 데이터베이스 연동과 정보 기록 자동화'&lt;/b&gt; 편으로 찾아오겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;24시간 구동되는 나만의 개인 연금 봇 제작기! 오늘도 흥미로우셨다면 구독과 응원 댓글 부탁드립니다. 감사합니다!&lt;/b&gt;&lt;/p&gt;</description>
      <author>myaistories</author>
      <guid isPermaLink="true">https://myaistories.tistory.com/22</guid>
      <comments>https://myaistories.tistory.com/22#entry22comment</comments>
      <pubDate>Wed, 27 May 2026 06:52:38 +0900</pubDate>
    </item>
    <item>
      <title>[시즌 2 - 최종화] &amp;quot;우연은 없다, 오직 데이터뿐&amp;quot; 비트코인 3개년 백테스팅 총결산 및 실전 봇 탑재 로드맵</title>
      <link>https://myaistories.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! AI와 함께 코딩 한 줄 모르는 상태에서 비트코인 자동매매 봇을 구축해 나가는 &lt;b&gt;My AI Stories&lt;/b&gt; 시리즈, 시즌 2의 8화(최종화)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시즌 1에서 실제 봇을 구축해 한 달 동안 돌려보며 **+14.8%**라는 수익률을 손에 쥐었을 때, 가장 먼저 들었던 의문이 있었습니다. &lt;i&gt;&quot;이건 그냥 운이 좋아서(뽀록) 벌린 게 아닐까? 하락장이나 횡보장이 닥치면 내 원금을 지켜낼 수 있을까?&quot;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차갑고 두려운 의문에 마침표를 찍기 위해 시작한 시즌 2의 3개월여 여정이 드디어 마무리를 맞이합니다. 우리는 세계 최대 거래소인 바이낸스(Binance)의 API를 긁어 **3년 치 비트코인 4시간봉 데이터(&lt;code&gt;btc_4h_3years.csv&lt;/code&gt;)**라는 거대한 시험지를 마련하고, 오직 팩트와 숫자로만 봇의 맷집을 담금질했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 최종화에서는 그동안 우리가 피와 땀으로 돌려보았던 모든 백테스팅 데이터를 총망라해 비교하고, 실제 우리의 24시간 실전 자동매매 봇에 주입할 &lt;b&gt;최종 전략 포트폴리오와 구축 로드맵&lt;/b&gt;을 선언합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 시즌 2 백테스팅 실험 데이터 대천하장사 결정전&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 비트코인 3개년 데이터 위에서 돌려본 수많은 시나리오들의 종합 성적표입니다. (왕복 수수료 0.2% 차감 기준)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;season2_final.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDhV5B/dJMcaaZH3wj/hhK4k6LoXkIzDwgOAaeig0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDhV5B/dJMcaaZH3wj/hhK4k6LoXkIzDwgOAaeig0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDhV5B/dJMcaaZH3wj/hhK4k6LoXkIzDwgOAaeig0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDhV5B%2FdJMcaaZH3wj%2FhhK4k6LoXkIzDwgOAaeig0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-filename=&quot;season2_final.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;실험 회차 및 전략 이름&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;세부 셋팅 조건&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;누적 수익률 (%)&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;최대 낙폭 (MDD, %)&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;핵심 성과 요약 및 의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;시즌 2 - 2화 기본 전략&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;20일 이동평균선 상향 돌파 (올인)&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;+114.97%&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;-32.96%&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;모든 실험의 비교 근본 대조군&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;시즌 2 - 3화 고정 손절선&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;20일선 돌파 + 고정 2.0% 손절&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;+106.12%&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;-32.96%&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;고정 비율 손절은 휩소에 당해 손해를 줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;시즌 2 - 4화 필터 전략&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;거래량 1.2배 초과 진입 (롱 단독)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;+141.74%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;-27.30%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;거래량 필터링으로 휩소 노이즈 극적 차단&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;ATR 2.0 동적손절 + 거래량 1.2배&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;+149.54%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;-27.26%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;시즌 2 단기 4시간봉 기준 최고 성과 달성&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;시즌 2 - 5화 롱/숏 &amp;amp; 불타기&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;롱 3분할 피라미딩 (불타기)&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;+112.53%&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;-25.74%&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;시드 분할 진입으로 MDD 방어 안전판 확보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;롱/숏 양방향 하이브리드 (올인)&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;+40.14%&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;-41.15%&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;V자 반등 및 숏 스퀴즈로 숏 포지션 참패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;시즌 2 - 6화 지표 필터링&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;볼밴 중간선 진입 + 상단 조기 익절&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;-4.65%&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;-47.49%&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;밴드 상단 이탈 추세를 억제해 수익 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;RSI 45 이하 과열 제한 진입&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;-16.58%&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;-28.31%&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;보조지표 과다 추가로 인한 오버피팅 실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;시즌 2 - 7화 판 갈아엎기&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;일봉(1D) 프레임 이동 (올인)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;+138.75%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;-23.49%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;시간축 확장으로 수수료 차감 최소화 및 최상급 MDD&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;일봉(1D) + 하프 켈리 자금배분 (8%)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;+8.92%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;-2.08%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;극단적 위험 회피형 (파산 리스크 제로 수준)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 3년의 백테스팅 데이터가 남겨준 3대 법칙&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 단순함의 승리 (Keep It Simple, Stupid!)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RSI 필터, 볼린저 밴드 익절, 숏 포지션 전환 등 머릿속으로 '그럴싸한' 안전 필터들을 계속 섞을수록 백테스팅 성적은 처참하게 무너졌습니다. 복잡한 지표 결합은 강세장의 추세를 중간에 잘라 먹거나 진짜 상승 추세 초입에서 봇의 진입을 막아버렸습니다. 가장 강력한 성적은 &lt;b&gt;'이동평균선 돌파'라는 큰 추세 뼈대에 '평균 대비 거래량 1.2배 초과'라는 아주 심플한 수문장 필터&lt;/b&gt; 하나만 달아두었을 때 달성되었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 비트코인 시장의 롱(Long) 편향성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하락장에서도 수익을 낼 것 같았던 숏(공매도) 베팅은 비트코인 특유 of 숏 스퀴즈(V자 반등 폭등)와 파생상품 비용(차입 수수료, 펀딩비) 장벽에 가로막혀 계좌를 녹이는 독이 되었습니다. 비트코인 시장을 상대할 때는 **하락장에서는 숏 포지션으로 덤비지 말고, 현금을 들고 아무것도 하지 않는 것(Only Long + Cash)**이 최고의 리스크 관리입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 전략이 아니라 판을 흔드는 리스크 튜닝&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수익률과 최대 낙폭(MDD)의 밸런스를 튜닝할 때, 매매 로직을 건드리는 것보다 **시간 프레임(4시간봉 -&amp;gt; 일봉)**을 키우거나, &lt;b&gt;켈리 공식(1회당 베팅 시드 8% 제한)을 통해 자금 관리 규칙&lt;/b&gt;을 도입하는 외적인 튜닝이 훨씬 직관적이고 강력한 결과를 가져왔습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 실전 자동매매 봇 적용을 위한 최종 로드맵&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 3개년 백테스팅에서 &lt;b&gt;각각 검증이 완료된 검증형 부품&lt;/b&gt;들을 조합하여 실전 운용 포트폴리오 시나리오를 구성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;※ 유의: 아래의 셋팅들은 각 단일 요소(ATR 동적 손절, 켈리 비율 등)의 백테스팅 검증을 마쳤으나, '시간 프레임 변환'과 'ATR 손절선' 및 '켈리 자금배분'을 한 바구니에 전부 섞은 복합 모델의 경우 아직 3개년 시뮬레이션에서 상호 시너지 검증이 온전히 이루어지지 않은 가설 영역입니다. 실전 주입 전 반드시 본인의 시뮬레이터로 개별 재검증을 권장합니다.&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  선택지 A: 공격적 추세 추종형 (4시간봉 검증 완료)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;시간 프레임&lt;/b&gt;: 4시간봉 (4h)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매매 필터&lt;/b&gt;: 거래량 1.2배 초과 돌파 + ATR 2.0 동적 손절선&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자금 관리&lt;/b&gt;: 자산의 100% 올인 매수 (추세 추종 모드)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;과거 데이터 성과&lt;/b&gt;: 누적 수익률 &lt;b&gt;+149.54%&lt;/b&gt;, MDD &lt;b&gt;-27.26%&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 선택지 B: 안정적 자산 우상향형 (일봉 검증 완료)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;시간 프레임&lt;/b&gt;: 일봉 (1D)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매매 필터&lt;/b&gt;: 거래량 1.2배 초과 돌파&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자금 관리&lt;/b&gt;: &lt;b&gt;하프 켈리 비율 적용 (진입 시 마다 총자산의 8%만 투입)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;과거 데이터 성과&lt;/b&gt;: 누적 수익률 &lt;b&gt;+8.92%&lt;/b&gt;, MDD &lt;b&gt;-2.08%&lt;/b&gt; (안정적인 복리 적금 모드)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 파이썬 매매 봇 소스코드 업그레이드 가이드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시즌 1에서 제작하여 구동 중인 실전 자동매매 봇의 거래 판단 모듈에 이번 시즌 2의 백테스팅 요소를 녹여 넣는 예시 코드입니다. 거래량 1.2배 돌파 필터링을 주입하는 핵심적인 논리 구조를 참고해 보시기 바랍니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# 자동매매 봇의 시그널 판단 엔진 예시 코드
def check_trade_signals(df):
    &quot;&quot;&quot;
    df: 바이낸스 API 등으로 조회한 최근 4시간봉 혹은 일봉 데이터프레임
    &quot;&quot;&quot;
    current_price = df.iloc[-1]['close']
    current_volume = df.iloc[-1]['volume']
    
    # 20일 이동평균선 계산 (4시간봉 기준 120캔들, 일봉 기준 20캔들)
    # (본인의 세부 운용 시간축에 맞춰 window 값을 튜닝해야 합니다)
    df['ma20'] = df['close'].rolling(window=120).mean()
    current_ma = df.iloc[-1]['ma20']
    
    # 최근 20개 캔들의 평균 거래량 계산
    df['vol_ma'] = df['volume'].rolling(window=20).mean()
    current_vol_ma = df.iloc[-1]['vol_ma']
    
    # 매수 신호 판정: 종가가 이평선 위에 있고, 거래량이 직전 20평균 대비 1.2배 초과 시
    if current_price &amp;gt; current_ma and current_volume &amp;gt; (current_vol_ma * 1.2):
        return &quot;BUY&quot;
    # 매도 신호 판정: 종가가 이평선 아래로 내려앉을 때
    elif current_price &amp;lt; current_ma:
        return &quot;SELL&quot;
        
    return &quot;HOLD&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;※ 본인의 실제 구동 시스템에 ATR 동적 손절선이나 켈리 공식 자금 배분(포지션 사이징)을 넣고자 하시는 분들은, API 주문 호출부(&lt;code&gt;create_order&lt;/code&gt;)에서 총 잔고 조회 후 계산된 켈리 비율(8%)만큼의 수량만 매수하도록 주문 수량을 계산하고, 매수 진입 시점의 ATR 값을 데이터베이스에 로컬 저장하여 감시하도록 주문 모듈을 확장 구성하여 탑재하시면 됩니다.&lt;/i&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 시즌 2를 마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시즌 1에서 '어떻게 봇을 켜고 주문을 보낼 것인가'라는 물리적 자동화를 배웠다면, 이번 시즌 2에서는 **'과거의 거친 폭풍 속에서 이 봇이 정말 살아남아 수익을 안겨줄 것인가'**라는 트레이딩의 정교한 철학과 리스크 제어법을 정립했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩 한 줄 몰라도 ChatGPT와 Gemini를 파트너 삼아 과거 3년의 대기록을 분석하고 계좌의 맷집을 길러낸 경험은, 앞으로 어떠한 변동성 장세가 오더라도 흔들리지 않고 매매 규칙을 고수할 수 있는 단단한 **'심리적 자산'**이 되어줄 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 My AI Stories 시즌 2의 백테스팅 연재를 함께 호흡하며 응원해 주신 모든 독자분께 진심으로 감사드립니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 더 진화된 봇과 함께 한층 더 스마트한 투자 생태계를 다지는 또 다른 여정으로 돌아오겠습니다. 여러분의 자동매매 봇이 언제나 든든한 우상향 곡선을 그리기를 기원합니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(참고: 본 포스팅에 기재된 시뮬레이션 결과는 과거 데이터 기반의 백테스팅 결과로, 미래의 투자 수익을 보장하지 않습니다. 모든 투자 책임은 본인에게 있습니다.)&lt;/i&gt;&lt;/p&gt;</description>
      <author>myaistories</author>
      <guid isPermaLink="true">https://myaistories.tistory.com/21</guid>
      <comments>https://myaistories.tistory.com/21#entry21comment</comments>
      <pubDate>Mon, 25 May 2026 22:16:30 +0900</pubDate>
    </item>
    <item>
      <title>[시즌 2 - 7화] &amp;quot;전략을 고칠 수 없다면 판을 바꿔라&amp;quot; 시간 프레임 확장과 켈리 공식 자금관리의 마법</title>
      <link>https://myaistories.tistory.com/20</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! AI와 함께 코딩 한 줄 모르는 상태에서 비트코인 자동매매 봇을 구축해 나가는 &lt;b&gt;My AI Stories&lt;/b&gt; 시리즈, 시즌 2의 7화입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 6화에서는 볼린저 밴드와 RSI 같은 보조지표 필터를 결합해 롱 전략을 고도화하려 했으나, 오히려 강한 상승 추세 진입을 방해하고 잦은 손실 거래만 누적되어 수익률이 마이너스로 꼬꾸라지는 '복잡성의 역설'을 목격했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전략이 복잡할수록 과거 데이터에만 과도하게 맞춰지는 오버피팅(Overfitting)의 위험에 처하기 쉽다는 뼈아픈 진실이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번 7화에서는 전략 로직을 억지로 뜯어고치지 않고, 시스템의 판 자체를 흔드는 &lt;b&gt;두 가지 패러다임 전환&lt;/b&gt;을 도입했습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;노이즈가 가득한 4시간봉 대신 더 거시적인 시야를 확보하는 '시간 프레임 확장(8시간봉 및 일봉)'&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매수할 때마다 수학적 확률에 기초해 최적의 자금을 배분하는 '켈리 공식(Kelly Criterion) 기반 자금 관리'&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 이평선 돌파 전략의 뼈대는 그대로 유지한 채, 시간축과 자금 투입 방식만 튜닝하여 3개년 비트코인 데이터를 돌린 결과는 놀랍도록 신선했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 시간 프레임 확장과 켈리 공식의 직관적 원리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  시간 프레임 리샘플링(Resampling)의 의의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 그동안 사용한 4시간봉은 시장의 잔파동(노이즈)이 꽤 많이 섞여 있습니다. 이 시간축을 8시간봉이나 일봉(24시간)으로 리샘플링하여 합치면, 자질구레한 흔들림은 캔들 내부로 숨어버리고 굵직한 거대 추세만 살아남습니다. 물리적으로 동일한 20일 이동평균선이라도, 기준 시간 프레임이 길어질수록 봇이 낚시성 돌파에 휩쓸리는 횟수가 극적으로 줄어듭니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  켈리 공식(Kelly Criterion) 자금관리란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;내 자산을 매번 올인하지 않고, 성공 확률과 기대 수익에 비례해 자산의 일부만 베팅한다면 어떻게 될까?&quot;라는 질문에서 출발한 수학 법칙입니다. 이평선 돌파 전략의 대략적인 승률(P)을 40%, 평균 이익/손실 비율(R)을 2.5배라고 가정할 때, 켈리 공식이 도출한 최적 베팅 비율은 다음과 같습니다. $$\text{Kelly Fraction (f)} = P - \frac{1 - P}{R} = 0.40 - \frac{0.60}{2.5} = 0.16 \text{ (16%)}$$ 이 수학 공식은 올인하지 말고 매회 전체 자산의 **16%**만 배분하는 것이 파산하지 않고 자산을 복리로 극대화하는 수학적 지점임을 말해줍니다. 이번 실험에서는 이 기준에 안전율 50%를 적용한 &lt;b&gt;'하프 켈리(Half-Kelly)' 원칙에 따라 매수 시 자금의 8%만 투입&lt;/b&gt;하도록 설계했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;timeframe_kelly.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bizJ8G/dJMcahkhYpK/GKOY7EW0IftX89dFpPyYbK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bizJ8G/dJMcahkhYpK/GKOY7EW0IftX89dFpPyYbK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bizJ8G/dJMcahkhYpK/GKOY7EW0IftX89dFpPyYbK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbizJ8G%2FdJMcahkhYpK%2FGKOY7EW0IftX89dFpPyYbK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-filename=&quot;timeframe_kelly.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 시간축 변환 및 켈리 공식 백테스팅 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 4시간봉 시세 데이터를 8시간봉과 일봉(1D)으로 변환(Resampling)하고, 하프 켈리 비율(8%)에 맞춰 매수 시 현금을 분할 투입하도록 개선한 파이썬 스크립트입니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import pandas as pd
import numpy as np

# 1. 4시간봉 데이터를 8시간봉 및 일봉(1D)으로 리샘플링
def resample_data(timeframe='4h'):
    df = pd.read_csv('btc_4h_3years.csv')
    df['timestamp'] = pd.to_datetime(df['timestamp'])
    df.set_index('timestamp', inplace=True)
    
    if timeframe == '4h':
        df.reset_index(inplace=True)
        return df
        
    rule = '8h' if timeframe == '8h' else '1D'
    
    # 고가, 저가, 시가, 종가 및 거래량 병합 규칙 적용
    resampled = df.resample(rule).agg({
        'open': 'first',
        'high': 'max',
        'low': 'min',
        'close': 'last',
        'volume': 'sum'
    }).dropna().reset_index()
    
    return resampled

# 2. 리샘플링 데이터 기준 백테스팅 및 켈리 공식 자금관리 적용
def run_backtest_timeframe(timeframe='4h', use_kelly=False):
    df = resample_data(timeframe)
    
    # 20일 기준 윈도우 조절 (4시간봉=120캔들, 8시간봉=60캔들, 일봉=20캔들)
    if timeframe == '4h':
        window = 120
    elif timeframe == '8h':
        window = 60
    else:
        window = 20
        
    df['ma'] = df['close'].rolling(window=window).mean()
    df['volume_ma'] = df['volume'].rolling(window=20).mean()
    df = df.dropna().reset_index(drop=True)
    
    init_balance = 10000.0
    balance = init_balance
    holdings = 0.0
    
    portfolio_values = []
    
    # 승률 40%, 손익비 2.5 기준 하프 켈리 비율인 8% 투자 비율 적용
    kelly_fraction = 0.08 if use_kelly else 1.0
    
    for i in range(len(df)):
        current_price = df.loc[i, 'close']
        current_ma = df.loc[i, 'ma']
        current_volume = df.loc[i, 'volume']
        current_vol_ma = df.loc[i, 'volume_ma']
        
        total_asset = balance + (holdings * current_price)
        portfolio_values.append(total_asset)
        
        # 1. 청산 (이평선 하향 돌파 시)
        if holdings &amp;gt; 0 and current_price &amp;lt; current_ma:
            balance += (holdings * current_price) * 0.999
            holdings = 0.0
            
        # 2. 매수 진입 (거래량 필터 적용)
        if current_price &amp;gt; current_ma and holdings == 0:
            if current_volume &amp;gt; (current_vol_ma * 1.2):
                # 켈리 공식 적용 시 자산의 일정 비율만 진입, 미적용 시 100% 올인
                invest_amt = balance * kelly_fraction
                holdings = (invest_amt * 0.999) / current_price
                balance -= invest_amt
                
    final_asset = balance + (holdings * df.iloc[-1]['close'])
    df['portfolio_value'] = portfolio_values
    df['cum_max'] = df['portfolio_value'].cummax()
    df['drawdown'] = (df['portfolio_value'] - df['cum_max']) / df['cum_max']
    mdd = df['drawdown'].min() * 100
    cumulative_return = ((final_asset - init_balance) / init_balance) * 100
    
    return len(df), cumulative_return, mdd
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 대망의 3개년 시간프레임 &amp;amp; 자금관리 성적표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 비트코인 3개년 시세를 기반으로, 올인 베팅 방식과 하프 켈리(8% 배분) 방식을 각 시간축별로 교차 검증한 결과입니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;시간 프레임 구분&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;캔들 수&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;자금 운용 방식&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;누적 수익률 (%)&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;최대 낙폭 (MDD, %)&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;성과 및 변동성 평가&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;4시간봉 (4h)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;7,323개&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;100% 올인 베팅&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;+141.74%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;-27.30%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;변동성이 크나 수익 기회가 잦음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;하프 켈리 자금관리 (8%)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;+9.62%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;-2.99%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;수익률은 대폭 감소, 낙폭은 극단적 안정(3% 미만)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;8시간봉 (8h)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;3,662개&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;100% 올인 베팅&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;+81.69%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;-35.30%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;애매한 프레임으로 휩소 노출 잦음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;하프 켈리 자금관리 (8%)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;+6.86%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;-3.37%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;변동성 극단 억제 모드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;일봉 (1D)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;1,222개&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;100% 올인 베팅 (최적)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;+138.75%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;-23.49%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;4시간봉 급 수익 달성 및 MDD 4%p 극적 개선&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;하프 켈리 자금관리 (8%)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;+8.92%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;-2.08%&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;사실상 적금 수준의 안전한 우상향 곡선&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 데이터가 알려준 위대한 진실&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 시간 프레임 확장의 위력: 일봉(1D)의 대발견&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 보통 자동매매 봇을 만들 때 짧은 봉(1분봉, 15분봉, 4시간봉)을 선호합니다. 더 자주 매매해서 돈을 빨리 불리고 싶기 때문이죠. 하지만 데이터를 보면 &lt;b&gt;일봉(1D) 올인 전략이 수익률 &lt;code&gt;+138.75%&lt;/code&gt;, MDD &lt;code&gt;-23.49%&lt;/code&gt;로 4시간봉 대비 전혀 꿀리지 않는 최상급 성적&lt;/b&gt;을 냈습니다. 4시간봉 대비 캔들 개수가 6분의 1 이하로 적어 매매 횟수와 수수료는 대폭 줄었음에도, 가짜 휩소 돌파에 낚이지 않고 큰 대세 상승장 흐름만 굵직하게 먹었기 때문에 가능한 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;※ 단, 기술적 관점에서 짚고 넘어가야 할 점은 시간 프레임이 일봉으로 확장되면서 &lt;b&gt;거래량 필터의 기준선(최근 20캔들 거래량 평균)의 물리적 일수도 일봉 기준 20일(4시간봉 기준은 3.3일)로 함께 확장&lt;/b&gt;되었다는 점입니다. 따라서 이 성과 개선은 단순한 시간축 확장뿐만 아니라, 거래량의 20일 장기 평균 필터가 가짜 돌파를 더욱 끈끈하게 잘 막아준 복합 효과가 작용한 결과입니다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 8시간봉(&lt;code&gt;8h&lt;/code&gt;)은 4시간봉의 변동성 단점과 일봉의 느린 템포 단점만 결합되어 오히려 MDD가 &lt;code&gt;-35.30%&lt;/code&gt;로 악화되었습니다. 시간 프레임을 선택할 때는 확실하게 큰 단위(일봉)로 이동하는 것이 훨씬 효율적입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 켈리 공식의 명암: '심리적 천국'과 '수익의 희생'&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;켈리 공식 자금관리를 적용했을 때 결과는 극단적이었습니다. 4시간봉에서 켈리를 쓰자 수익률은 &lt;code&gt;+9.62%&lt;/code&gt;로 내려앉았지만, &lt;b&gt;MDD가 무려 &lt;code&gt;-2.99%&lt;/code&gt;로 사실상 원금 파손 리스크가 제로에 가까운 안전성&lt;/b&gt;을 보여주었습니다. 일봉의 경우 MDD가 단 **&lt;code&gt;-2.08%&lt;/code&gt;**였습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;※ 참고로 이번 코드에 적용된 자금 관리는 매 거래의 승률과 손익비를 실시간으로 동적 계산해 베팅 크기를 늘리고 줄이는 고난도 동적 켈리가 아닌, &lt;b&gt;과거 고정 통계값(승률 40%, 손익비 2.5)을 기반으로 자산의 일정 비율(8%)만 진입하도록 설계한 정적 하프 켈리 자금배분&lt;/b&gt; 방식입니다. 켈리의 수학적 아이디어를 보수적인 고정 자금 비율 관리에 안전망으로 차용한 개념입니다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매매 한 번에 8%의 적은 시드만 태우기 때문에, 아무리 큰 폭락을 맞아 손절이 나가더라도 전체 자산에는 상처조차 나지 않는 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;올인 전략&lt;/b&gt;: 고점 대비 자산이 27% 깎이는 롤러코스터 심장 마비를 견뎌야 함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;켈리 전략&lt;/b&gt;: 계좌가 2~3% 수준에서만 살짝 출렁이며 예금처럼 잔잔하게 우상향함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수십억 단위의 큰 시드를 굴리거나 절대로 원금을 잃어서는 안 되는 안정형 투자자라면 켈리 공식(혹은 하프 켈리)을 이용한 자금 비중 제어가 필수적입니다. 반대로 공격적인 복리 성장을 원한다면 켈리 비율을 높이거나(예: 30~40%) 적절한 불타기를 섞어 타협점을 찾아야 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div class=&quot;adsense-inline&quot; style=&quot;text-align: center; margin: 20px 0;&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;pub-9294653930170981&quot; data-ad-slot=&quot;3533399718&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
       (adsbygoogle = window.adsbygoogle || []).push({});
  &lt;/script&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 에필로그 및 다음 화 예고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 백테스팅을 통해 전략 로직의 매수/매도 규칙을 복잡하게 손대지 않고도, &lt;b&gt;캔들 시간 프레임과 매수 비중 조절&lt;/b&gt;이라는 외적인 매개변수 조절만으로 계좌의 성격을 완전히 180도 바꿀 수 있음을 깨달았습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;잔노이즈를 걸러내고 효율을 올리고 싶다면: &lt;b&gt;일봉(1D) 프레임 도입&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;심장 쫄깃한 변동성을 억누르고 안전형 자산으로 굴리고 싶다면: &lt;b&gt;켈리 자금배분 기법 도입&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비전공자인 저도 이제 제 투자 성향과 시드 규모에 맞는 자금 관리 모델을 명확한 데이터로 튜닝할 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 8화에서는 지금까지 시즌 2에서 돌려본 모든 백테스팅 데이터를 총망라하고, &lt;b&gt;시즌 2를 최종 결산&lt;/b&gt;해 보겠습니다. 어떤 조합(시간축 + 필터 + 자금관리)이 우리의 실전 매매 봇을 위한 최종 낙찰 전략이 될지, 그리고 실제 파이썬 자동화 봇에 이 설계를 어떻게 코드로 주입하는지 최종 정리로 찾아뵙겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분은 투자할 때 캔들 시간 프레임을 주로 어디에 두고 보시나요? 매수 시 시드의 몇 %를 투입하시는지 여러분만의 기준을 댓글로 나누어 주세요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(참고: 본 포스팅에 기재된 시뮬레이션 결과는 과거 데이터 기반의 백테스팅 결과로, 미래의 투자 수익을 보장하지 않습니다. 모든 투자 책임은 본인에게 있습니다.)&lt;/i&gt;&lt;/p&gt;</description>
      <author>myaistories</author>
      <guid isPermaLink="true">https://myaistories.tistory.com/20</guid>
      <comments>https://myaistories.tistory.com/20#entry20comment</comments>
      <pubDate>Mon, 25 May 2026 22:04:42 +0900</pubDate>
    </item>
  </channel>
</rss>