init
This commit is contained in:
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
12
.idea/AssetMonitor.iml
generated
Normal file
12
.idea/AssetMonitor.iml
generated
Normal 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>
|
||||||
33
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
33
.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal 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
7
.idea/misc.xml
generated
Normal 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
8
.idea/modules.xml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
39
README.md
39
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%)
|
||||||
|
- 모니터링할 주식 목록
|
||||||
|
|
||||||
|
|||||||
101
config.py
Normal file
101
config.py
Normal 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
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
yfinance
|
||||||
|
pandas
|
||||||
|
numpy
|
||||||
121
stock_monitor.py
Normal file
121
stock_monitor.py
Normal 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()
|
||||||
Reference in New Issue
Block a user