This commit is contained in:
dsyoon
2025-08-04 21:36:38 +09:00
parent 3f6ca0ce47
commit ddb0a40a4a

129
stock_simulation.py Normal file
View File

@@ -0,0 +1,129 @@
import math
import requests
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
plt.rcParams['font.family'] ='AppleGothic'
plt.rcParams['axes.unicode_minus'] =False
from config import *
from stock_monitor import calculate_technical_indicators, detect_turnaround_signal
# 비트/알트코인 KRW 마켓 식별: 문자열 "-KRW" 포함 여부로 간단 구분
INTERVAL_MAP = {
60: "60m", # 1시간 (yfinance)
240: "4h", # 4시간 (yfinance)
}
BITHUMB_MAX_COUNT = 3000 # API 최대 3000 캔들
def fetch_coin_history_bithumb(symbol: str, interval_minutes: int, days: int) -> pd.DataFrame:
"""빗썸 API를 이용해 최근 `days`일 코인 데이터 수집 (interval 60 / 240)"""
if interval_minutes not in (60, 240):
raise ValueError("Bithumb API only supports 60 or 240 minutes in this helper")
minutes = interval_minutes
count = int(math.ceil(days * 24 * 60 / minutes)) + 10 # 여유분 10 캔들
count = min(count, BITHUMB_MAX_COUNT)
url = f"https://api.bithumb.com/v1/candles/minutes/{minutes}?market=KRW-{symbol}&count={count}"
res = requests.get(url, timeout=5)
res.raise_for_status()
raw = res.json()
if not isinstance(raw, list) or len(raw) == 0:
raise RuntimeError("Empty response from Bithumb API")
df_temp = pd.DataFrame(raw)
# API 반환: [timestamp, open, close, high, low, volume] 순
df_temp = df_temp.sort_index(ascending=False) # 최신순, 뒤집어서 역순 전달
data = pd.DataFrame()
# data.columns = ['datetime', 'open', 'close', 'high', 'low', 'volume']
# data['datetime'] = pd.to_datetime(data_temp['candle_date_time_kst'])
data['datetime'] = pd.to_datetime(df_temp['candle_date_time_kst'], format='%Y-%m-%dT%H:%M:%S')
data['Open'] = df_temp['opening_price']
data['Close'] = df_temp['trade_price']
data['High'] = df_temp['high_price']
data['Low'] = df_temp['low_price']
data['Volume'] = df_temp['candle_acc_trade_volume']
data = data.set_index('datetime')
data = data.astype(float)
data["datetime"] = data.index
data = data.set_index("datetime").sort_index() # 시간 오름차순
return data
def fetch_price_history(symbol: str, interval_minutes: int, days: int = 7) -> pd.DataFrame:
"""최근 `days`일 데이터(캔들)를 가져온다. 코인(-KRW)은 빗썸, 그 외 yfinance."""
if symbol in KR_COINS:
base_symbol = symbol.replace("-KRW", "")
return fetch_coin_history_bithumb(base_symbol, interval_minutes, days)
# -------- 주식/ETF/해외코인 (yfinance) --------
if interval_minutes not in INTERVAL_MAP:
raise ValueError("interval must be 60 or 240")
interval_str = INTERVAL_MAP[interval_minutes]
df = yf.download(
tickers=symbol,
period=f"{days}d",
interval=interval_str,
progress=False,
)
if df.empty:
raise RuntimeError("No data fetched. Check symbol or interval support.")
return df
def run_simulation(symbol: str, interval_minutes: int, days: int = 7):
data = fetch_price_history(symbol, interval_minutes, days)
data = calculate_technical_indicators(data)
alerts = [] # (timestamp, price)
# 시계열 순회하며 알림 조건 체크
for i in range(len(data)):
slice_df = data.iloc[: i + 1]
info = detect_turnaround_signal(symbol, slice_df, interval=interval_minutes)
if info and info["alert"]:
alerts.append((slice_df.index[-1], slice_df["Close"].iloc[-1]))
# 8월 3일 이후의 매수 신호만 고려
alerts = [(time, price) for time, price in alerts if time > pd.Timestamp('2025-08-03')]
# Plot
plt.figure(figsize=(12, 6))
plt.plot(data.index, data["Close"], label="종가", color="black")
plt.plot(data.index, data["MA5"], label="MA5", color="orange", linewidth=1)
plt.plot(data.index, data["MA20"], label="MA20", color="blue", linewidth=1)
plt.plot(data.index, data["MA40"], label="MA40", color="green", linewidth=1)
# Bollinger Bands
plt.plot(data.index, data["Upper"], label="볼린저 Upper", color="grey", linestyle="--", linewidth=1)
plt.plot(data.index, data["Lower"], label="볼린저 Lower", color="grey", linestyle="--", linewidth=1)
plt.fill_between(data.index, data["Lower"], data["Upper"], color="grey", alpha=0.1)
if alerts:
times, prices = zip(*alerts)
plt.scatter(times, prices, facecolors='none', edgecolors='red', linewidths=2, s=150, zorder=6, label='매수신호')
plt.title(f"{symbol} 시뮬레이션 {interval_minutes}분봉 (최근 {days}일)")
plt.xlabel("날짜")
plt.ylabel("가격")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
if __name__ == "__main__":
symbol = 'WLD'
interval = 240
days = 7
run_simulation(symbol, interval, days)