2025. 1. 17. 13:00ㆍ프로젝트
이 전 글들이 궁금하다면 ?
0. 사이트를 만드려는 이유
https://hsjoo126.tistory.com/80
1-1. 프로젝트 가능성 보기
https://hsjoo126.tistory.com/81
pandas 와 jupyter 이용해서 테스트해보기
https://hsjoo126.tistory.com/82
1-2. 기획 단계 - 디자인, 와이어 프레임, ERD 등
https://hsjoo126.tistory.com/83
2. 개발 단계 - 계획짜기, 구현해보기
https://hsjoo126.tistory.com/84
2-1. 개발 단계 - 배당지불일, 시장별 티커리스트 구하기
https://hsjoo126.tistory.com/85
2-2. 개발 단계 - 코드 정리
https://hsjoo126.tistory.com/86
2-3. 개발 단계 - 사이트에 적용하기(장고)
https://hsjoo126.tistory.com/87
이전 글에서 내가 원하는 주식 정보를 불러오고 그걸 사이트에 적용하는 것까지 해보았다!
이제 과부하를 줄일 방법을 찾아야한다.
현재 돌아가고 있는 로직
1. financedatareader를 사용해 나스닥에 있는 주식을 다 가져온 후 거기서 티커만 걸러내서 보여준다.
2. 뽑아낸 티커리스트를 yfinance에 알려준다.(조회한다.)
3. yfinance에서 티커별로 필요한 정보들을 가져온다.
- 현재주가, 마지막 배당금, 마지막 배당일, 배당 수익률, 시총
4. 배당수익률이 4~7%인 애들만 필터링한다.
5. html로 넘겨준다.
생각해보기
아마, 나스닥에 있는 주식을 다 조회해야하니까 시간이 오래걸리는 거 같다.
거기다가 yfinance에서 그 티커들을 한 번 더 조회해야하니까 ... 시간이 두 배로 걸리는 셈이다.
저 부분만 줄여도 홈페이지 로딩이 더 빨리 될 거 같다.
해결방법 생각해보기
1. 애초에 배당률 4~7%인 애들만 조회할 순 없을까 ?
- 그러려면 yfinance에 있는 배당률 데이터를 기준으로 골라내야한다.
- 근데 기본적으로 티커리스트가 제공이 되어야한다.
- 그럼 어쩔 수 없이 지금같은 방식을 계속 사용할 수 밖에 ....
- yfinance에서 시장별로 티커들을 조회할수 있는 게 없어서.. 이런 방식이 된 건데 ... 어렵네
- 로직을 뒤엎어야하는데.. 어떻게 함요..? ㅎ
2. redis사용해보기
- redis를 써서 데이터를 가져온 다음에 일정 시간동안 캐시된 데이터를 사용하는 건 ?
- 이렇게 되면 캐시된 데이터를 보여주니까, 빠르게 보여줄 수 있지만
- 현재 주가가.. 자연스럽게 과거 데이터가 됨
3. 사용자한테 기다리라고 하기
- "데이터를 로딩 중입니다" 화면을 띄우는 방법
- 지금 상태에서 로직을 더 추가할 필요는 없어서 나한테는 편하겠지만...
- 내가 사용자 입장이면 이 사이트 안 쓰고 싶을듯..ㅎ
음 이정도로 생각 나는데.
우리 사이트는 배당관련된 정보를 보여주는 사이트니까,
실시간으로 현재주가를 알려주는 건 뒤로 미뤄도 괜찮겠다는 생각이 들었다.
2. redis사용해보기 를 채택하도록하자!
오케이.
지금 생각으로는 redis + crontab 조합으로 충분히 구현할 수 있을거 같다.
celery도 생각해봤는데,, 내 사이트는 그렇게 큰 사이트가 아니니까 ... 응응..
다음과 같은 순서로 작업할 거 같다.
- redis랑 django-crontab설치하기
- settings.py 에 cache관련 코드 넣기
- views.py에 redis 설정해주기
- crontab설정하기
예상되는 로직 순서는 이렇다.
#로직 순서
http://127.0.0.1:8000/stocks/middle/로 요청.
views.py에서 Redis 캐시 확인:
캐시에 데이터가 있으면 반환.
(만약 캐시에 데이터 없으면 실시간으로 데이터 수집, 캐시에 저장)
템플릿에서 데이터를 출력.
+주기적으로 크론탭이 데이터 업로드
그럼 한 번 해보자!!
1. pip 다 설치하기!
pip install redis
pip install django-redis
pip install django-crontab
2. settings.py 설정하기!
- crontab 추가하기
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
#장고 크론탭 추가
'django_crontab',
]
- caches 관련 설정, redis넣어주고 timeout도 설정하기
#redis 설정
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1', # Redis 서버 URL
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
},
'TIMEOUT': 1800, # 30분 TTL
}
}
- django-crontab 설정하기, 시간 설정과 파일 위치를 알려주었다
#django-crontab설정
CRONJOBS = [
('*/30 * * * *', 'stocks.cron.update_stock_data'), # 매 30분마다 데이터 갱신
]
참고.
corntab설명은 딱히 하지 않을 것이다.
corntab 관련해서 적은 글이니 볼 사람들은 보자!
https://hsjoo126.tistory.com/59
자! 그럼 다 알고 있다는 가정 하에 다시 이어나가자~
3. 크론탭한테 일 시킬 로직 짜기!
전에 완성했던 코드에 레디스 관련 설정만 덧붙였다
레디스 키 설정 해주고, 데이터 수집해서, 4-7% 배당률인 애들만 필터링 하고 레디스에 데이터를 저장해주었다
#위치는? 프로젝트/앱/cron.py
import yfinance as yf
import FinanceDataReader as fdr
from django.core.cache import cache
import pandas as pd
def update_stock_data():
# Redis 캐시 키 설정
keys = {
"4_to_7_percent": "stock_data_4_to_7",
}
# 1. 나스닥 상위 종목가져오기
nasdaq_stocks = fdr.StockListing('NASDAQ')
tickers = nasdaq_stocks['Symbol'][:100]
stock_data_4_to_7 = []
# 2. 각 티커에 대해 데이터 수집
for ticker_symbol in tickers:
try:
ticker = yf.Ticker(ticker_symbol)
# 데이터 수집
current_price = ticker.info.get('currentPrice','정보없음')
dividends = ticker.dividends
last_dividend_value = dividends.iloc[-1] if not dividends.empty else None
# 마지막 배당일
info = ticker.calendar
dividend_date = None
# info가 dict 형식인 경우
if isinstance(info, dict) and 'Dividend Date' in info:
dividend_date = info['Dividend Date']
# info가 DataFrame 형식인 경우
elif isinstance(info, pd.DataFrame) and 'Dividends' in info.index:
dividend_date = info.loc['Dividends', 0]
yield_value = ticker.info.get('dividendYield', 0) * 100
market_cap = ticker.info.get('marketCap')
# 4~7% 배당률 데이터
if 4 <= yield_value <= 7:
stock_data_4_to_7.append({
'ticker': ticker_symbol,
'current_price': current_price,
'last_dividend': last_dividend_value,
'dividend_date': dividend_date,
'dividend_yield': round(yield_value, 2),
'market_cap': "{:,}".format(market_cap) if market_cap else None,
})
except Exception as e:
print(f"Error fetching data for {ticker_symbol}: {e}")
# 3. Redis에 데이터 저장 (30분 TTL)
cache.set(keys["4_to_7_percent"], stock_data_4_to_7)
print("각 페이지별 주식 데이터가 업데이트 되었습니다!")
4. view 로직 추가!
사용자가 페이지에 들어왔는데, redis에서 불러올 데이터가 없을 것을 대비해
view에도 똑같은 로직을 넣어주었다!
#views.py
from django.shortcuts import render
import yfinance as yf
import pandas as pd
import FinanceDataReader as fdr
from django.core.cache import cache
def middle(request):
# Redis에서 데이터 가져오기
cache_key = "stock_data_4_to_7"
stock_data = cache.get(cache_key)
# Redis에 데이터가 없을 경우, 실시간으로 데이터 수집
if not stock_data:
nasdaq_stocks = fdr.StockListing('NASDAQ')
tickers = nasdaq_stocks['Symbol'][:100]
stock_data = []
for ticker_symbol in tickers:
try:
ticker = yf.Ticker(ticker_symbol)
# 데이터 수집
current_price = ticker.info.get('currentPrice','정보없음')
dividends = ticker.dividends
last_dividend_value = dividends.iloc[-1] if not dividends.empty else None
# 마지막 배당일
info = ticker.calendar
dividend_date = None
# info가 dict 형식인 경우
if isinstance(info, dict) and 'Dividend Date' in info:
dividend_date = info['Dividend Date']
# info가 DataFrame 형식인 경우
elif isinstance(info, pd.DataFrame) and 'Dividends' in info.index:
dividend_date = info.loc['Dividends', 0]
yield_value = ticker.info.get('dividendYield', 0) * 100
market_cap = ticker.info.get('marketCap')
# 4~7% 배당률 필터링
if 4 <= yield_value <= 7:
stock_data.append({
'ticker': ticker_symbol,
'current_price': current_price,
'last_dividend': last_dividend_value,
'dividend_date': dividend_date,
'dividend_yield': round(yield_value, 2),
'market_cap': "{:,}".format(market_cap) if market_cap else None,
})
except Exception as e:
print(f"Error fetching data for {ticker_symbol}: {e}")
cache.set(cache_key, stock_data)
# 템플릿에 데이터 전달
return render(request, "stocks/middle.html", {"stocks_data": stock_data})
5. 실행방법
1. redis를 켠다.
redis-server
2. crontab을 실행시킨다.
python manage.py crontab add
3. 장고 서버를 실행한다.
python manage.py runserver
6. 실행모습
처음 서버를 켜서 페이지에 접속해보면, 뜨는데 시간이 오래걸린다.
왜냐면 redis에 아무 데이터도 올라가있지 않기 때문이다.
그래서 view에 있는 if not 로직을 타고 데이터를 열심히 수집해 원하는 결과를 보여준다.
그리고 이 상태에서 새로고침을 눌러보면 ! 전과는 다르게 GET 요청이 들어온 데이터를 바로 보여주는 것을 알 수 있다!
맨처음엔 13:14:55, 수집 완료 후엔 13:16:08,
이후엔 13:17~ 대가 계속 뜬 것을 볼 수 있음.
7. crontab은 잘되고 있나요??
view로직과 redis는 실행이 잘 된 것을 확인할 수 있다.
그럼 crontab은 ??
실험을 위해 작업을 3분마다 실행하도록 바꿔주자.
add로 작업을 추가해주고 show를 통해 작업이 잘 실행되고 있는지 확인하자.
실행은 됐지만, 내가 원하는 시간에 정확히 실행이 되는지는 알지 못한다, 그래서 data.log를 찍어줄 것이다.
크론탭 설정에 data.log가 어디에 생길지 설정해주고
#settings.py
#django-crontab설정
CRONJOBS = [
('*/3 * * * *', 'stocks.cron.update_stock_data', '>> /Users/t2023-m0088/Desktop/stocks/dividend_stock/data.log'), # 매 30분마다 데이터 갱신
]
나는 프로젝트 폴더에 생기도록 절대경로를 써주었다
만약 crontab이 제 시간에 잘 실행된다면! cron.py 에 써놓았던 프린트문이 출력될 것이다.
data.log 가 생겼고 확인해보면?
오! print문이 잘 뜬 것을 볼 수 있다.
다만 데이터 불러오는데 시간이 좀 걸려서,
설정해놓은 시간 3분이 됐을 때, data.log 가 생겼고 데이터를 다 불러온 후에 프린트문이 떴다.
아무튼 잘되니까!!
홈페이지도 잘 뜬다! 야호~!!
휴 이제! 배당률 4~7% 페이지로 실험을 마쳤으니,
7%이상인 페이지 코드도 마저 완성하고
이제 개인 페이지를 완성하러 가야겠다..!!
그건 다음 글에서~!!
배당률 7% 이상 추가한 코드들(사실 별거 없음 ㅎ)
#views.py
#배당률 7% 초과
def high(request):
# Redis에서 데이터 가져오기
cache_key = "stock_data_above_7"
stock_data = cache.get(cache_key)
# Redis에 데이터가 없을 경우, 실시간으로 데이터 수집
if not stock_data:
nasdaq_stocks = fdr.StockListing('NASDAQ')
tickers = nasdaq_stocks['Symbol'][:100]
stock_data = []
for ticker_symbol in tickers:
try:
ticker = yf.Ticker(ticker_symbol)
# 데이터 수집
current_price = ticker.info.get('currentPrice','정보없음')
dividends = ticker.dividends
last_dividend_value = dividends.iloc[-1] if not dividends.empty else None
# 마지막 배당일
info = ticker.calendar
dividend_date = None
# info가 dict 형식인 경우
if isinstance(info, dict) and 'Dividend Date' in info:
dividend_date = info['Dividend Date']
# info가 DataFrame 형식인 경우
elif isinstance(info, pd.DataFrame) and 'Dividends' in info.index:
dividend_date = info.loc['Dividends', 0]
yield_value = ticker.info.get('dividendYield', 0) * 100
market_cap = ticker.info.get('marketCap')
# 7%이상 배당률 필터링
if yield_value > 7:
stock_data.append({
'ticker': ticker_symbol,
'current_price': current_price,
'last_dividend': last_dividend_value,
'dividend_date': dividend_date,
'dividend_yield': round(yield_value, 2),
'market_cap': "{:,}".format(market_cap) if market_cap else None,
})
except Exception as e:
print(f"Error fetching data for {ticker_symbol}: {e}")
cache.set(cache_key, stock_data)
# 템플릿에 데이터 전달
return render(request, "stocks/high.html", {"stocks_data": stock_data})
# cron.py
def update_stock_data():
# Redis 캐시 키 설정
keys = {
"4_to_7_percent": "stock_data_4_to_7",
"above_7_percent": "stock_data_above_7",
}
# 1. 나스닥 상위 종목가져오기
nasdaq_stocks = fdr.StockListing('NASDAQ')
tickers = nasdaq_stocks['Symbol'][:100] # 상위 30개 종목만 선택
stock_data_4_to_7 = []
stock_data_above_7 = []
# 2. 각 티커에 대해 데이터 수집
for ticker_symbol in tickers:
try:
ticker = yf.Ticker(ticker_symbol)
# 데이터 수집
current_price = ticker.info.get('currentPrice', '정보없음')
dividends = ticker.dividends
last_dividend_value = dividends.iloc[-1] if not dividends.empty else None
# 마지막 배당일
info = ticker.calendar
dividend_date = None
# info가 dict 형식인 경우
if isinstance(info, dict) and 'Dividend Date' in info:
dividend_date = info['Dividend Date']
# info가 DataFrame 형식인 경우
elif isinstance(info, pd.DataFrame) and 'Dividends' in info.index:
dividend_date = info.loc['Dividends', 0]
yield_value = ticker.info.get('dividendYield', 0) * 100
market_cap = ticker.info.get('marketCap')
# 4~7% 배당률 데이터
if 4 <= yield_value <= 7:
stock_data_4_to_7.append({
'ticker': ticker_symbol,
'current_price': current_price,
'last_dividend': last_dividend_value,
'dividend_date': dividend_date,
'dividend_yield': round(yield_value, 2),
'market_cap': "{:,}".format(market_cap) if market_cap else None,
})
# 7% 이상 배당률 데이터
if yield_value > 7:
stock_data_above_7.append({
'ticker': ticker_symbol,
'current_price': current_price,
'last_dividend': last_dividend_value,
'dividend_date': dividend_date,
'dividend_yield': round(yield_value, 2),
'market_cap': "{:,}".format(market_cap) if market_cap else None,
})
except Exception as e:
print(f"Error fetching data for {ticker_symbol}: {e}")
# 3. Redis에 데이터 저장 (30분 TTL)
cache.set(keys["4_to_7_percent"], stock_data_4_to_7)
cache.set(keys["above_7_percent"], stock_data_above_7)
print("각 페이지별 주식 데이터가 업데이트 되었습니다!")
#high.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>주식 정보</title>
<style>
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
</style>
</head>
<body>
<h1>배당률 7% 초과 주식들</h1>
<table>
<thead>
<tr>
<th>Ticker</th>
<th>현재 주가</th>
<th>마지막 배당금</th>
<th>배당일</th>
<th>배당수익률</th>
<th>시가총액</th>
</tr>
</thead>
<tbody>
{% for stock in stocks_data %}
<tr>
<td>{{ stock.ticker }}</td>
<td>{{ stock.current_price }}</td>
<td>{{ stock.last_dividend }}</td>
<td>{{ stock.dividend_date }}</td>
<td>{{ stock.dividend_yield }}%</td>
<td>{{ stock.market_cap }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
나중에 해결해야하는 로직
1. 데이터 로딩 시간을 줄일 수 없을까 ?
2. 데이터 로딩이 너무 길어서 100개 정도 밖에 안 넣었는데 다 조회가 안되는 거 같음..
3. views.py 중복 코드 없애기
내일 할 일
- 개인 페이지 만들기
- views.py 중복 코드 없애기
- 데이터 로딩시간 줄이기
- 티커 나스닥 뿐만아니라 다른 시장도 넣어서 조회하기
'프로젝트' 카테고리의 다른 글
나만의 배당주 사이트 만들기) 2-5. 개발 단계 - 주식별 동적인 페이지 만들기, (시총, 주가내역, 배당 내역 등) (0) | 2025.01.21 |
---|---|
나만의 배당주 사이트 만들기) 3. 디자인 - 잠깐 쉬어가자! 그런김에 디자인~! (0) | 2025.01.20 |
나만의 배당주 사이트 만들기) 2-3. 개발 단계 - 사이트에 적용하기(장고) (0) | 2025.01.16 |
나만의 배당주 사이트 만들기) 2-2. 개발 단계 - 코드 정리(티커리스트, 마지막 배당금, 마지막 배당일, 배당률, 시총) (0) | 2025.01.15 |
나만의 배당주 사이트 만들기) 2-1. 개발 단계 - 배당지불일, 시장별 티커리스트 구하기 feat. 웹크롤링, Yfinance, FinanceDataReader (0) | 2025.01.14 |