#!/usr/bin/env python3 """ upbit_db_init.py — upbit_quant_db MariaDB 테이블 초기화 스크립트 ================================================================= 실행: python3 upbit_db_init.py - 필요한 테이블이 없으면 생성 (IF NOT EXISTS → 재실행 안전) - env_config에 초기 기본값 row 삽입 """ import sys import pymysql import pymysql.cursors import logging logging.basicConfig(level=logging.INFO, format="[%(asctime)s] %(message)s", datefmt="%H:%M:%S") logger = logging.getLogger("UpbitDBInit") DB_CFG = dict( host="192.168.0.141", port=3306, user="jae", password="1234", database="upbit_quant_db", charset="utf8mb4", autocommit=True, cursorclass=pymysql.cursors.DictCursor, connect_timeout=10, ) DDL_STATEMENTS = [ # ── 1. 현재 보유 포지션 ────────────────────────────────────────────── """ CREATE TABLE IF NOT EXISTS active_trades ( code VARCHAR(20) NOT NULL PRIMARY KEY COMMENT '마켓코드 (KRW-BTC)', name VARCHAR(50) COMMENT '종목명', strategy VARCHAR(50) COMMENT '전략명', avg_buy_price DECIMAL(20,8) COMMENT '평균 매수가', current_price DECIMAL(20,8) COMMENT '현재가', stop_price DECIMAL(20,8) COMMENT '손절가', target_price DECIMAL(20,8) COMMENT '목표가', max_price DECIMAL(20,8) COMMENT '보유 중 최고가 (트레일링스탑용)', atr_entry DECIMAL(20,8) COMMENT '진입 시점 ATR (변동성)', target_qty DECIMAL(30,10) COMMENT '목표 수량', current_qty DECIMAL(30,10) COMMENT '현재 수량', total_invested DECIMAL(20,2) COMMENT '총 투자금액 (원)', status VARCHAR(20) DEFAULT 'HOLDING' COMMENT '상태 (HOLDING)', buy_date DATETIME COMMENT '매수 시각', updated_at DATETIME COMMENT '최종 업데이트', rsi DECIMAL(8,4) COMMENT '진입 시 RSI', volume_ratio DECIMAL(8,4) COMMENT '거래량 비율', tail_length_pct DECIMAL(8,4) COMMENT '꼬리 길이 (%)' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='현재 보유 포지션' """, # ── 2. 매매 기록 ──────────────────────────────────────────────────── """ CREATE TABLE IF NOT EXISTS trade_history ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, code VARCHAR(20) COMMENT '마켓코드', name VARCHAR(50) COMMENT '종목명', strategy VARCHAR(50) COMMENT '전략명', buy_price DECIMAL(20,8) COMMENT '매수가', sell_price DECIMAL(20,8) COMMENT '매도가', qty DECIMAL(30,10) COMMENT '거래 수량', profit_rate DECIMAL(10,4) COMMENT '수익률 (%)', realized_pnl DECIMAL(20,2) COMMENT '실현 손익 (원)', hold_minutes INT COMMENT '보유 시간 (분)', buy_date DATETIME COMMENT '매수 시각', sell_date DATETIME COMMENT '매도 시각', sell_reason VARCHAR(200) COMMENT '매도 사유', rsi DECIMAL(8,4) COMMENT '진입 시 RSI', volume_ratio DECIMAL(8,4) COMMENT '거래량 비율', tail_length_pct DECIMAL(8,4) COMMENT '꼬리 길이 (%)', INDEX idx_sell_date (sell_date), INDEX idx_code (code), INDEX idx_strategy (strategy) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='매매 기록' """, # ── 3. 전략 설정값 (Upbit 전용) ────────────────────────────────────── """ CREATE TABLE IF NOT EXISTS env_config ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, created_at DATETIME DEFAULT NOW() COMMENT '생성 시각', -- 업비트 API 키 UPBIT_ACCESS_KEY VARCHAR(200) DEFAULT '' COMMENT '업비트 Access Key', UPBIT_SECRET_KEY VARCHAR(200) DEFAULT '' COMMENT '업비트 Secret Key', -- 알림 (Mattermost) MM_SERVER_URL VARCHAR(200) DEFAULT '' COMMENT 'MM 서버 URL', MM_BOT_TOKEN_ VARCHAR(200) DEFAULT '' COMMENT 'MM 봇 토큰', MATTERMOST_CHANNEL VARCHAR(100) DEFAULT 'upbit' COMMENT 'MM 채널명', -- 포지션 관리 MAX_STOCKS VARCHAR(20) DEFAULT '5' COMMENT '최대 보유 코인 수', SLOT_MONEY_DEFAULT VARCHAR(20) DEFAULT '100000' COMMENT '코인당 투자금액 (원)', -- 손익 기준 STOP_LOSS_PCT VARCHAR(20) DEFAULT '-0.02' COMMENT '손절 비율 (음수, 예:-0.02)', TAKE_PROFIT_PCT VARCHAR(20) DEFAULT '0.05' COMMENT '익절 비율 (예:0.05)', MAX_LOSS_PER_TRADE_KRW VARCHAR(20) DEFAULT '50000' COMMENT '거래당 최대 원화 손실 한도', -- 어깨 매도 (수익 보존) SHOULDER_CUT_PCT VARCHAR(20) DEFAULT '0.03' COMMENT '어깨매도: 고점 대비 하락률', SHOULDER_MIN_HIGH_PCT VARCHAR(20) DEFAULT '0.01' COMMENT '어깨매도: 발동 최소 이익률', SHOULDER_MIN_NET_PCT VARCHAR(20) DEFAULT '0.001' COMMENT '어깨매도: 수수료 반영 최소 이익', -- ATR 스캘핑 엑시트 SCALP_ATR_UP_MULT VARCHAR(20) DEFAULT '1.0' COMMENT 'ATR 스캘핑: 상승 배수', SCALP_ATR_DOWN_MULT VARCHAR(20) DEFAULT '0.2' COMMENT 'ATR 스캘핑: 하락 배수', SCALP_ATR_DROP_MULT VARCHAR(20) DEFAULT '1.0' COMMENT 'ATR 스캘핑: 낙폭 배수', -- ATR 손절/목표가 배수 STOP_ATR_MULTIPLIER_TAIL VARCHAR(20) DEFAULT '2.5' COMMENT '손절선: 진입가 - ATR * 배수', TARGET_ATR_MULTIPLIER_TAIL VARCHAR(20) DEFAULT '7.0' COMMENT '목표가: 진입가 + ATR * 배수', -- 스캔 조건 MIN_DROP_RATE VARCHAR(20) DEFAULT '0.03' COMMENT '매수 스캔: 최소 낙폭 (예:0.03)', MIN_RECOVERY_RATIO VARCHAR(20) DEFAULT '0.30' COMMENT '매수 스캔: 최소 회복률', MAX_RECOVERY_RATIO VARCHAR(20) DEFAULT '0.80' COMMENT '매수 스캔: 최대 회복률', HIGH_PRICE_CHASE_THRESHOLD VARCHAR(20) DEFAULT '0.96' COMMENT '고점 추격 방지 임계값', RSI_OVERHEAT_THRESHOLD VARCHAR(20) DEFAULT '78.0' COMMENT 'RSI 과열 임계값', -- 꼬리봉 조건 TAIL_RATIO_MIN VARCHAR(20) DEFAULT '1.5' COMMENT '꼬리/몸통 최소 비율', TAIL_PCT_MIN VARCHAR(20) DEFAULT '0.003' COMMENT '꼬리 최소 % (예:0.003)', TAIL_SCORE_BASE VARCHAR(20) DEFAULT '5.0' COMMENT '꼬리 기본 점수', TAIL_SCORE_RATIO_MULT VARCHAR(20) DEFAULT '2.0' COMMENT '꼬리 비율 점수 가중치', -- 기타 매매 파라미터 REENTRY_COOLDOWN_SEC VARCHAR(20) DEFAULT '300' COMMENT '재진입 쿨다운 (초)', ROUND_TRIP_COST_PCT VARCHAR(20) DEFAULT '0.001' COMMENT '왕복 수수료율 (업비트 0.05%×2)', MIN_HOLD_AFTER_BUY_SEC VARCHAR(20) DEFAULT '10.0' COMMENT '매수 후 최소 보유 시간 (초)', -- 스캔 주기 UPBIT_SCAN_INTERVAL_SEC VARCHAR(20) DEFAULT '60' COMMENT '매수 스캔 주기 (초)', UPBIT_BUY_TOP_N VARCHAR(20) DEFAULT '2' COMMENT '스캔 후 상위 N개 매수', UPBIT_CANDLE_UNIT VARCHAR(20) DEFAULT '3' COMMENT '스캔용 분봉 단위 (3/5/15/60)' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='전략 설정값 (업비트 전용)' """, # ── 4. Key-Value 저장소 (API 키 등) ────────────────────────────────── """ CREATE TABLE IF NOT EXISTS kv_store ( k VARCHAR(100) NOT NULL PRIMARY KEY COMMENT '키', v TEXT COMMENT '값' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Key-Value 저장소' """, # ── 5. 스캔 후보 목록 ─────────────────────────────────────────────── """ CREATE TABLE IF NOT EXISTS target_candidates ( code VARCHAR(20) NOT NULL PRIMARY KEY COMMENT '마켓코드', name VARCHAR(50) COMMENT '코인명', score DECIMAL(10,4) COMMENT '후보 점수', price DECIMAL(20,8) COMMENT '스캔 당시 가격', scan_time DATETIME COMMENT '스캔 시각', updated_at DATETIME COMMENT '최종 업데이트' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='스캔 후보 목록' """, # ── 6. 업비트 분봉 데이터 (백테스트용) ────────────────────────────── """ CREATE TABLE IF NOT EXISTS upbit_candles ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, code VARCHAR(20) NOT NULL COMMENT '마켓코드 (KRW-BTC)', candle_time VARCHAR(12) NOT NULL COMMENT '봉 시작시각 YYYYMMDDHHMI', timeframe SMALLINT NOT NULL DEFAULT 3 COMMENT '봉 단위 (3=3분, 60=60분봉)', open_price DECIMAL(20,8) COMMENT '시가', high_price DECIMAL(20,8) COMMENT '고가', low_price DECIMAL(20,8) COMMENT '저가', close_price DECIMAL(20,8) COMMENT '종가', volume DECIMAL(30,8) COMMENT '체결량', is_confirmed TINYINT DEFAULT 1 COMMENT '완성된 봉 여부', UNIQUE KEY uk_code_time_tf (code, candle_time, timeframe), INDEX idx_code_tf (code, timeframe), INDEX idx_time (candle_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='업비트 분봉 OHLCV (백테스트용)' """, ] # env_config 초기 기본값 INSERT (이미 row가 있으면 스킵) ENV_CONFIG_DEFAULT_INSERT = """ INSERT IGNORE INTO env_config (id, created_at) VALUES (1, NOW()) """ def run_init(): logger.info("📦 upbit_quant_db 테이블 초기화 시작...") try: conn = pymysql.connect(**DB_CFG) except Exception as e: logger.error(f"❌ DB 연결 실패: {e}") sys.exit(1) try: with conn.cursor() as cur: for sql in DDL_STATEMENTS: table_name = sql.strip().split("TABLE IF NOT EXISTS")[1].split("(")[0].strip() cur.execute(sql) logger.info(f" ✅ 테이블 생성/확인: {table_name}") # env_config 초기 row 삽입 (최초 1회) cur.execute("SELECT COUNT(*) as cnt FROM env_config") if cur.fetchone()["cnt"] == 0: cur.execute(ENV_CONFIG_DEFAULT_INSERT) logger.info(" ✅ env_config 초기값 row 삽입 완료") else: logger.info(" ℹ️ env_config row 이미 존재 — 스킵") # kv_store 기본 키 삽입 kv_defaults = [ ("UPBIT_ACCESS_KEY", ""), ("UPBIT_SECRET_KEY", ""), ("UPBIT_SCAN_INTERVAL_SEC", "60"), ("UPBIT_BUY_TOP_N", "2"), ] for k, v in kv_defaults: cur.execute( "INSERT IGNORE INTO kv_store (k, v) VALUES (%s, %s)", (k, v) ) logger.info(" ✅ kv_store 기본 키 삽입 완료") conn.commit() logger.info("🎉 upbit_quant_db 초기화 완료!") except Exception as e: logger.error(f"❌ 초기화 중 오류: {e}") import traceback; traceback.print_exc() sys.exit(1) finally: conn.close() if __name__ == "__main__": run_init()