This commit is contained in:
dsyoon
2025-04-25 21:56:19 +09:00
parent 4ba735e225
commit a8c3185d10
11 changed files with 338 additions and 1 deletions

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

12
.idea/AssetMonitor.iml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="coin" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

View File

@@ -0,0 +1,33 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="13">
<item index="0" class="java.lang.String" itemvalue="pywin32" />
<item index="1" class="java.lang.String" itemvalue="tensorflow" />
<item index="2" class="java.lang.String" itemvalue="pandas_datareader" />
<item index="3" class="java.lang.String" itemvalue="slack-sdk" />
<item index="4" class="java.lang.String" itemvalue="langchain_text_splitters" />
<item index="5" class="java.lang.String" itemvalue="ner_metrics" />
<item index="6" class="java.lang.String" itemvalue="html_table_parser" />
<item index="7" class="java.lang.String" itemvalue="sse_starlette" />
<item index="8" class="java.lang.String" itemvalue="langchain_experimental" />
<item index="9" class="java.lang.String" itemvalue="langchain_community" />
<item index="10" class="java.lang.String" itemvalue="langchain_huggingface" />
<item index="11" class="java.lang.String" itemvalue="unstructured_pytesseract" />
<item index="12" class="java.lang.String" itemvalue="rank_bm25" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="N806" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="coin" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="coin" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/AssetMonitor.iml" filepath="$PROJECT_DIR$/.idea/AssetMonitor.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -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%)
- 모니터링할 주식 목록

101
config.py Normal file
View File

@@ -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% 근접 시 알림

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
yfinance
pandas
numpy

121
stock_monitor.py Normal file
View File

@@ -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()