diff --git a/.gitignore b/.gitignore index d2814ef..585bfa7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ build/ dist/ wheels/ *.egg-info -upbit_db_init.py # Virtual environments .venv diff --git a/upbit_db_init.py b/upbit_db_init.py new file mode 100644 index 0000000..ecf68d2 --- /dev/null +++ b/upbit_db_init.py @@ -0,0 +1,226 @@ +#!/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()