From 0f935381123efd9e70b97dbfff614ada3e0db345 Mon Sep 17 00:00:00 2001 From: dsyoon Date: Sun, 20 Jul 2025 19:48:44 +0900 Subject: [PATCH] init --- README.md | 116 +++++++++++++++++++++++++++++++++++++---------- config.py | 1 + stock_monitor.py | 23 ++++++++-- 3 files changed, 112 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 85075d2..0cb3869 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,107 @@ -# 주식 모니터링 시스템 +# AssetMonitor 주식·코인 모니터링 시스템 -이 프로그램은 주식 시장을 모니터링하고 볼린저 밴드 기반의 매수 시그널을 텔레그램으로 알려주는 시스템입니다. +## 개요 +`AssetMonitor`는 주식‧ETF 및 암호화폐 시장을 실시간으로 감시하여 Bollinger Band, RSI, MACD, 이동평균(Golden-Cross), 거래량 등을 종합 분석한 **매수 후보(signals)**를 텔레그램으로 통보하는 자동화 봇입니다. -## 주요 기능 +--- -- 미국 주식 데이터 자동 수집 -- 볼린저 밴드 분석 -- 텔레그램 알림 발송 +## 주요 구성 파일 +| 파일 | 설명 | +|------|------| +| `config.py` | ✅ API 토큰, 텔레그램 채널 ID, 볼린저 밴드/임계값, 모니터링 자산 목록(KR_COINS, US_STOCKS, KR_ETFS) 등 전역 설정을 보관합니다. | +| `stock_monitor.py` | 시스템의 핵심 로직이 담긴 실행 스크립트입니다.
• 데이터 수집 ⇒ 기술적 지표 계산 ⇒ 매수 신호 판단 ⇒ 메시지 포맷팅/발송
• `schedule` 라이브러리로 정해진 시간마다 작업을 자동 실행합니다. | +| `requirements.txt` | 프로젝트 의존 패키지를 명시합니다. | + +--- + +## 데이터 흐름 +1. **스케줄 트리거** (`run_schedule`) + 지정된 시각에 각 모니터링 함수가 호출됩니다. +2. **데이터 획득** + *주식 / ETF*: `FinanceDataReader` + *암호화폐*: 빗썸 **240분 봉** Open API +3. **기술적 지표 계산** (`calculate_technical_indicators`) + - Bollinger Band (기간 20, ±2σ) + - RSI(14) + - MACD(12-26-9) + - 단/중/장기 이동평균선(MA5/20/60) + - 거래량 MA5 +4. **매수 후보 판정** (`check_buy_signals`) + - 볼린저 하단 근접도 < `BOLLINGER_THRESHOLD` (기본 0.10) + - RSI < 30 (과매도) + - MACD 골든크로스 + - MA5가 MA20 상향 돌파 + - 거래량 급증(현재 > MA5 × 1.5) + 위 조건 중 ① Bollinger & RSI 동시 충족 **또는** ② 최소 2개 이상(+Bollinger 또는 RSI 포함) 만족 시 `buy=True`. +5. **알림 발송** (`send_*_telegram_message`) + multiprocessing Pool을 이용해 다중 메시지를 병렬로 전송합니다. + +--- + +## 스케줄 테이블 (기본값) +| 대상 | 실행 시각(서버 기준) | 호출 함수 | +|------|----------------------|-----------| +| KRW 코인 | 매시간 04, 34분 | `monitor_coins()` | +| 미국 주식 / ETF | 05:10, 16:30, 23:30 | `monitor_us_stocks()` | +| 한국 ETF / 주식 | 07:10, 18:20 | `monitor_kr_stocks()` | + +> 시간은 `config.py`가 아닌 `stock_monitor.py`의 `run_schedule()` 내부에 하드코딩되어 있습니다. 필요 시 직접 수정하세요. + +--- ## 설치 방법 - -1. 필요한 패키지 설치: +1. Python ≥ 3.9 환경을 준비합니다. +2. 저장소를 클론하고 디렉터리로 이동: ```bash -pip install -r requirements.txt +$ git clone +$ cd AssetMonitor ``` +3. 패키지 설치: +```bash +$ pip install -r requirements.txt +``` +4. **보안 키 등록** + 민감 정보는 코드에 직접 기록하지 말고 *환경 변수*로 주입하기를 권장합니다. +```bash +# zsh 예시 +export COIN_TELEGRAM_BOT_TOKEN="" +export STOCK_TELEGRAM_BOT_TOKEN="" +export COIN_TELEGRAM_CHAT_ID="" +export STOCK_TELEGRAM_CHAT_ID="" +``` + 또는 `config.py` 내부 상수를 직접 수정할 수 있습니다. -2. 환경 변수 설정: -`.env` 파일을 생성하고 다음 내용을 추가: -``` -TELEGRAM_BOT_TOKEN=your_bot_token -TELEGRAM_CHAT_ID=your_chat_id -``` +--- ## 사용 방법 - -프로그램 실행: ```bash -python stock_monitor.py +$ python stock_monitor.py +``` +스크립트가 백그라운드에서 무한 루프로 동작하며 지정된 시간마다 텔레그램 알림을 전송합니다. + +### Docker(선택) +컨테이너 실행 예시: +```dockerfile +FROM python:3.11-slim +WORKDIR /app +COPY . . +RUN pip install -r requirements.txt +CMD ["python", "stock_monitor.py"] ``` -## 설정 +--- -- `config.py` 파일에서 다음 설정을 변경할 수 있습니다: - - 볼린저 밴드 기간 (기본값: 20일) - - 표준편차 승수 (기본값: 2) - - 알림 임계값 (기본값: 10%) - - 모니터링할 주식 목록 +## 커스터마이징 +- **자산 목록 추가/삭제**: `config.py`의 `KR_COINS`, `US_STOCKS`, `KR_ETFS` 사전을 편집합니다. +- **임계값·기간 조정**: `BOLLINGER_PERIOD`, `BOLLINGER_STD`, `BOLLINGER_THRESHOLD`, `BUY_THRESHOLD` 등 변경. + +--- + +## 한계 및 면책 조항 +본 프로젝트는 교육·연구 목적의 오픈소스 예제로, 투자 손실에 대해 어떠한 책임도 지지 않습니다. 실거래에 사용하려면 충분한 검증과 백테스트를 진행하십시오. + +--- + +## 라이선스 +MIT (프로젝트 루트의 `LICENSE` 파일 참조, 미존재 시 필요에 따라 추가하세요.) diff --git a/config.py b/config.py index 0436d8f..caca66d 100644 --- a/config.py +++ b/config.py @@ -12,6 +12,7 @@ BOLLINGER_PERIOD = 20 # 볼린저 밴드 기간 BOLLINGER_STD = 2 # 표준편차 승수 BOLLINGER_THRESHOLD = 0.10 # 하단 밴드 대비 10% 근접 시 알림 BUY_THRESHOLD = 0.15 +BREAKOUT_LOOKBACK = 30 # U자 반등 후 돌파 판단에 사용할 과거 캔들 수 (4시간봉 기준 약 5일) KR_COINS = { "ADA": "ADA", diff --git a/stock_monitor.py b/stock_monitor.py index 61d937e..3fd7bf8 100644 --- a/stock_monitor.py +++ b/stock_monitor.py @@ -109,6 +109,16 @@ def check_buy_signals(symbol, data): distance = (current_price - lower_band) / (upper_band - lower_band) bb_signal = distance < BOLLINGER_THRESHOLD + + # U자 반등 후 이전 고점 돌파 여부 계산 (BREAKOUT) + breakout_signal = False + if len(data) >= BREAKOUT_LOOKBACK + 1: + window_close = data['Close'].iloc[-BREAKOUT_LOOKBACK-1:-1] + prev_high = window_close.max() + prev_low = window_close.min() + # 가격이 충분히 내려갔다가(BUY_THRESHOLD 비율) 다시 이전 고점을 돌파하면 breakout으로 간주 + if prev_high > 0 and (prev_high - prev_low) / prev_high > BUY_THRESHOLD and current_price > prev_high: + breakout_signal = True # RSI 과매도 신호 (RSI < 30) if not isinstance(latest['Upper'], float): @@ -129,7 +139,8 @@ def check_buy_signals(symbol, data): 'rsi_signal': rsi_signal, 'macd_signal': macd_signal, 'ma_signal': ma_signal, - 'volume_signal': volume_signal + 'volume_signal': volume_signal, + 'breakout_signal': breakout_signal } # 최소 3개 이상의 신호가 동시에 발생할 때 매수 신호로 간주 @@ -145,7 +156,7 @@ def check_buy_signals(symbol, data): 'signal_line': latest['Signal'].iloc[0], 'buy_signals': buy_signals, 'signal_count': signal_count, - 'buy': (bb_signal and rsi_signal) or (signal_count >= 2 and (bb_signal or rsi_signal)) + 'buy': breakout_signal or ((bb_signal and rsi_signal) or (signal_count >= 2 and (bb_signal or rsi_signal))) } else: rsi_signal = latest['RSI'] < 30 @@ -165,7 +176,8 @@ def check_buy_signals(symbol, data): 'rsi_signal': rsi_signal, 'macd_signal': macd_signal, 'ma_signal': ma_signal, - 'volume_signal': volume_signal + 'volume_signal': volume_signal, + 'breakout_signal': breakout_signal } # 최소 3개 이상의 신호가 동시에 발생할 때 매수 신호로 간주 @@ -181,7 +193,7 @@ def check_buy_signals(symbol, data): 'signal_line': latest['Signal'], 'buy_signals': buy_signals, 'signal_count': signal_count, - 'buy': (bb_signal and rsi_signal) or (signal_count >= 2 and (bb_signal or rsi_signal)) + 'buy': breakout_signal or ((bb_signal and rsi_signal) or (signal_count >= 2 and (bb_signal or rsi_signal))) } def format_message(info, market_type): @@ -211,6 +223,9 @@ def format_message(info, market_type): if info['buy_signals']['volume_signal']: message += "- 거래량 급증" count += 1 + if info['buy_signals'].get('breakout_signal'): + message += "- U자 반등 돌파" + count += 1 message += "\n" message = message.replace("{count}", str(count)) return message