diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/AssetMonitor.iml b/.idea/AssetMonitor.iml new file mode 100644 index 0000000..eb70346 --- /dev/null +++ b/.idea/AssetMonitor.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..445a3f4 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,33 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..26d5e4d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..06fde9a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 6654144..85075d2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,39 @@ -# AssetMonitor +# 주식 모니터링 시스템 + +이 프로그램은 주식 시장을 모니터링하고 볼린저 밴드 기반의 매수 시그널을 텔레그램으로 알려주는 시스템입니다. + +## 주요 기능 + +- 미국 주식 데이터 자동 수집 +- 볼린저 밴드 분석 +- 텔레그램 알림 발송 + +## 설치 방법 + +1. 필요한 패키지 설치: +```bash +pip install -r requirements.txt +``` + +2. 환경 변수 설정: +`.env` 파일을 생성하고 다음 내용을 추가: +``` +TELEGRAM_BOT_TOKEN=your_bot_token +TELEGRAM_CHAT_ID=your_chat_id +``` + +## 사용 방법 + +프로그램 실행: +```bash +python stock_monitor.py +``` + +## 설정 + +- `config.py` 파일에서 다음 설정을 변경할 수 있습니다: + - 볼린저 밴드 기간 (기본값: 20일) + - 표준편차 승수 (기본값: 2) + - 알림 임계값 (기본값: 10%) + - 모니터링할 주식 목록 diff --git a/config.py b/config.py new file mode 100644 index 0000000..72b4251 --- /dev/null +++ b/config.py @@ -0,0 +1,101 @@ +import os + +# 텔레그램 설정 +TELEGRAM_BOT_TOKEN = "6435061393:AAHOh9wB5yGNGUdb3SfCYJrrWTBe7wgConM" +TELEGRAM_CHAT_ID = '574661323' + +# 주식 설정 +US_STOCKS = { + 'VOO': 'Vanguard S&P 500 ETF', + 'SQQQ': 'ProShares UltraPro Short QQQ', + 'QID': 'ProShares UltraShort QQQ', + 'PSQ': 'ProShares Short QQQ', + 'TQQQ': 'ProShares UltraPro QQQ', + 'QQQ': 'Invesco QQQ Trust', + 'SCO': 'ProShares UltraShort Bloomberg Crude Oil', + 'UCO': 'ProShares Ultra Bloomberg Crude Oil', + 'GLL': 'ProShares UltraShort Gold', + 'UGL': 'ProShares Ultra Gold', + 'SOXS': 'Direxion Daily Semiconductor Bear -3X Shares', + 'SOXL': 'Direxion Daily Semiconductor Bull 3X Shares', + 'FNGD': 'MicroSectors™ FANG+™ Index -3X Inverse Leveraged ETN', + 'FNGU': 'MicroSectors™ FANG+™ Index 3X Leveraged ETN', + 'FXI': 'iShares China Large-Cap ETF', + + 'AAPL': 'Apple', + 'MSFT': 'Microsoft', + 'GOOG': 'Alphabet C', + 'AMZN': 'Amazon.com', + 'AVGO': 'Broadcom', + 'NVDA': 'NVIDIA', + 'UNH': 'UnitedHealth', + 'TSM': 'Taiwan Semiconductor', + 'JNJ': 'Johnson & Johnson (JNJ)', + 'TCTZF': 'Tencent Holdings', + 'V': 'Visa A', + 'WMT': 'Walmart', + 'XOM': 'Exxon Mobil', + 'JPM': 'JPMorgan', + 'MA': 'Mastercard', + 'CVX': 'Chevron Corp', + 'HD': 'Home Depot', + 'BAC': 'Bank of America', + 'KO': 'Coca-Cola', + 'COST': 'Costco', + 'DIS': 'Walt Disney', + 'VZ': 'Verizon', + 'CSCO': 'Cisco', + 'ORCL': 'Oracle', + 'NKE': 'Nike', + 'ACN': 'Accenture', + 'ADBE': 'Adobe', + 'CRM': 'Salesforce.com', + 'INTC': 'Intel', + 'QCOM': 'Qualcomm', + 'AMD': 'AMD', + 'MS': 'Morgan Stanley', + 'T': 'AT&T', + 'HON': 'Honeywell', + 'IBM': 'IBM', + 'DQ': 'Daqo New Energy Corp ADR', + 'EBAY': 'eBay Inc', + 'NTAP': 'NetApp Inc', + 'ASML': 'ASML Holding NV ADR', + 'CPNG': 'Coupang LLC', + 'X': 'United States Steel Corporation', + 'BABA': 'Alibaba Group Holdings Ltd ADR' +} + +# 한국 ETF 설정 +KR_ETFS = { + "251340.KS": 'KODEX 코스닥150선물인버스', + "233740.KS": 'KODEX 코스닥150 레버리지', + "252670.KS": 'KODEX 200선물인버스2X', + "122630.KS": 'KODEX 레버리지', + "114800.KS": 'KODEX 인버스', + "283580.KS": 'KODEX 중국본토CSI300', + "256750.KS": 'KODEX 심천ChiNext(합성)', + "185680.KS": 'KODEX 미국S&P바이오(합성)', + "218420.KS": 'KODEX 미국S&P에너지(합성)', + "132030.KS": 'KODEX 골드선물(H)', + "138920.KS": 'KODEX 콩선물(H)', + "271060.KS": 'KODEX 3대농산물선물(H)', + "117700.KS": 'KODEX 건설', + "266420.KS": 'KODEX 헬스케어', + "276990.KS": 'KODEX 글로벌4차산업로보틱스(합성)', + "244580.KS": 'KODEX 바이오', + "091160.KS": 'KODEX 반도체', + "140700.KS": 'KODEX 보험', + "266410.KS": 'KODEX 필수소비재', + "305720.KS": 'KODEX 2차전지산업', + "266390.KS": 'KODEX 경기소비재', + "117680.KS": 'KODEX 철강', + "117460.KS": 'KODEX 에너지화학', + "091170.KS": 'KODEX 은행', + "376410.KS": 'TIGER 탄소효율그린뉴딜' +} + +# 볼린저 밴드 설정 +BOLLINGER_PERIOD = 20 # 볼린저 밴드 기간 +BOLLINGER_STD = 2 # 표준편차 승수 +ALERT_THRESHOLD = 0.15 # 하단 밴드 대비 10% 근접 시 알림 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ce48393 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +yfinance +pandas +numpy \ No newline at end of file diff --git a/stock_monitor.py b/stock_monitor.py new file mode 100644 index 0000000..5634cca --- /dev/null +++ b/stock_monitor.py @@ -0,0 +1,121 @@ +import yfinance as yf +import pandas as pd +from datetime import datetime, timedelta +import telegram +import time +import asyncio +from multiprocessing import Pool +from config import * + +def send(text): + client = telegram.Bot(token=TELEGRAM_BOT_TOKEN) + asyncio.run(client.send_message(chat_id=TELEGRAM_CHAT_ID, text=text)) + return + +def send_telegram_message(message): + pool = Pool(12) + pool.map(send, [message]) + +def calculate_bollinger_bands(data): + data['MA'] = data['Close'].rolling(window=BOLLINGER_PERIOD).mean() + data['STD'] = data['Close'].rolling(window=BOLLINGER_PERIOD).std() + data['Upper'] = data['MA'] + (BOLLINGER_STD * data['STD']) + data['Lower'] = data['MA'] - (BOLLINGER_STD * data['STD']) + return data + +def check_bollinger_bands(symbol, data): + if len(data) < BOLLINGER_PERIOD: + return None + latest = data.iloc[-1] + upper_band = latest['Upper'].iloc[0] + lower_band = latest['Lower'].iloc[0] + current_price = latest['Close'].iloc[0] + distance = (current_price - lower_band) / (upper_band - lower_band) + return { + 'symbol': symbol, + 'price': current_price, + 'lower_band': lower_band, + 'distance': distance + } + +def get_stock_data(symbol, retries=3): + for attempt in range(retries): + try: + end = datetime.now() + start = end - timedelta(days=60) + data = yf.download( + symbol, + start=start.strftime('%Y-%m-%d'), + end=end.strftime('%Y-%m-%d'), + interval='1d', + auto_adjust=True, + progress=False + ) + if not data.empty: + return data + print(f"No data received for {symbol}, attempt {attempt + 1}") + time.sleep(2) + except Exception as e: + print(f"Attempt {attempt + 1} failed for {symbol}: {str(e)}") + if attempt < retries - 1: + time.sleep(5) + continue + return None + +def sendAlertMsg(info, market="US"): + if market == "US": + message = "🔔 [US] {} ({}) 현재가: ${:.2f}, 근접도: {:.2f}%".format(info['name'], info['symbol'], info['price'], info['distance']) + else: + message = "🔔 [KR] {} ({}) 현재가: ₩{:.0f}, 근접도: {:.2f}%".format(info['name'], info['symbol'].replace('.KS', ''), info['price'], info['distance']) + + try: + send_telegram_message(message) + except Exception as e: + print(f"Error sending Telegram message: {str(e)}") + return + +def monitor_stocks(): + # 미국 주식 모니터링 + print("Monitoring US stocks...") + for symbol in US_STOCKS: + data = get_stock_data(symbol) + if data is not None and not data.empty: + try: + data = calculate_bollinger_bands(data) + info = check_bollinger_bands(symbol, data) + info['name'] = US_STOCKS[symbol] + print(" - {} ({}): {:.2f} ({:.2f})".format(info['name'], symbol, info['price'], info['distance'])) + + if info['distance'] <= ALERT_THRESHOLD: + sendAlertMsg(info, "US") + print(f"Alert generated for {symbol}") + except Exception as e: + print(f"Error processing data for {symbol}: {str(e)}") + else: + print(f"Data for {symbol} is empty or None.") + time.sleep(0.5) + + # 한국 ETF 모니터링 + print("\nMonitoring Korean ETFs...") + for symbol in KR_ETFS: + data = get_stock_data(symbol) + if data is not None and not data.empty: + try: + data = calculate_bollinger_bands(data) + info = check_bollinger_bands(symbol, data) + info['name'] = KR_ETFS[symbol] + print(" - {} ({}): {:.2f} ({:.2f})".format(info['name'], symbol, info['price'], info['distance'])) + + if info['distance'] <= ALERT_THRESHOLD: + sendAlertMsg(info, "KR") + print(f"Alert generated for {symbol}") + except Exception as e: + print(f"Error processing data for {symbol}: {str(e)}") + else: + print(f"Data for {symbol} is empty or None.") + time.sleep(0.5) + + return + +if __name__ == "__main__": + monitor_stocks()