Spaces:
Running
Running
import streamlit as st | |
import pandas as pd | |
import numpy as np | |
from prophet import Prophet | |
import plotly.express as px | |
import seaborn as sns | |
import matplotlib.pyplot as plt | |
from datetime import date | |
from pathlib import Path | |
# ------------------------------------------------- | |
# CONFIG ------------------------------------------ | |
# ------------------------------------------------- | |
CSV_PATH = Path("price_data.csv") | |
PARQUET_PATH = Path("domae-202503.parquet") # 1996โ1993-03 ๊ฐ๊ฒฉ ๋ฐ์ดํฐ | |
MACRO_START, MACRO_END = "1996-01-01", "2030-12-31" | |
MICRO_START, MICRO_END = "2020-01-01", "2026-12-31" | |
st.set_page_config(page_title="ํ๋ชฉ๋ณ ๊ฐ๊ฒฉ ์์ธก", page_icon="๐", layout="wide") | |
# ------------------------------------------------- | |
# UTILITIES --------------------------------------- | |
# ------------------------------------------------- | |
def load_data() -> pd.DataFrame: | |
"""Load price data from Parquet if available, else CSV.""" | |
if PARQUET_PATH.exists(): | |
df = pd.read_parquet(PARQUET_PATH) | |
elif CSV_PATH.exists(): | |
df = pd.read_csv(CSV_PATH) | |
else: | |
st.error("๋ฐ์ดํฐ ํ์ผ์ ์ฐพ์ ์ ์์ต๋๋ค. price_data.csv ๋๋ domae-202503.parquet" ) | |
st.stop() | |
# ํ์คํ | |
df["date"] = pd.to_datetime(df["date"]) | |
df.sort_values("date", inplace=True) | |
return df | |
def get_items(df: pd.DataFrame): | |
return sorted(df["item"].unique()) | |
def fit_prophet(df: pd.DataFrame, horizon_end: str): | |
m = Prophet(yearly_seasonality=True, weekly_seasonality=False, daily_seasonality=False) | |
m.fit(df.rename(columns={"date": "ds", "price": "y"})) | |
future = m.make_future_dataframe(periods=(pd.Timestamp(horizon_end) - df["date"].max()).days, freq="D") | |
forecast = m.predict(future) | |
return m, forecast | |
# ------------------------------------------------- | |
# LOAD DATA --------------------------------------- | |
# ------------------------------------------------- | |
raw_df = load_data() | |
st.sidebar.header("๐ ํ๋ชฉ ์ ํ") | |
selected_item = st.sidebar.selectbox("ํ๋ชฉ", get_items(raw_df)) | |
current_date = date.today() | |
st.sidebar.caption(f"์ค๋: {current_date}") | |
item_df = raw_df.query("item == @selected_item").copy() | |
if item_df.empty: | |
st.error("์ ํํ ํ๋ชฉ ๋ฐ์ดํฐ ์์") | |
st.stop() | |
# ------------------------------------------------- | |
# PLOTS ------------------------------------------- | |
# ------------------------------------------------- | |
st.header(f"๐ {selected_item} ๊ฐ๊ฒฉ ์์ธก ๋์๋ณด๋") | |
# Macro forecast 1996โ2030 | |
macro_df = item_df[item_df["date"] >= MACRO_START] | |
m_macro, fc_macro = fit_prophet(macro_df, MACRO_END) | |
fig_macro = px.line(fc_macro, x="ds", y="yhat", title="Macro Forecast 1996โ2030") | |
fig_macro.add_scatter(x=macro_df["date"], y=macro_df["price"], mode="lines", name="Actual") | |
st.plotly_chart(fig_macro, use_container_width=True) | |
latest_price = macro_df.iloc[-1]["price"] | |
macro_pred = fc_macro.loc[fc_macro["ds"] == MACRO_END, "yhat"].iloc[0] | |
macro_pct = (macro_pred - latest_price) / latest_price * 100 | |
st.metric("2030 ์์ธก๊ฐ", f"{macro_pred:,.0f}", f"{macro_pct:+.1f}%") | |
# Micro forecast 2024โ2026 | |
st.subheader("๐ 2024โ2026 ๋จ๊ธฐ ์์ธก") | |
micro_df = item_df[item_df["date"] >= MICRO_START] | |
m_micro, fc_micro = fit_prophet(micro_df, MICRO_END) | |
fig_micro = px.line(fc_micro, x="ds", y="yhat", title="Micro Forecast 2024โ2026") | |
fig_micro.add_scatter(x=micro_df["date"], y=micro_df["price"], mode="lines", name="Actual") | |
st.plotly_chart(fig_micro, use_container_width=True) | |
micro_pred = fc_micro.loc[fc_micro["ds"] == MICRO_END, "yhat"].iloc[0] | |
micro_pct = (micro_pred - latest_price) / latest_price * 100 | |
st.metric("2026 ์์ธก๊ฐ", f"{micro_pred:,.0f}", f"{micro_pct:+.1f}%") | |
# Seasonality components | |
with st.expander("๐ ์์ฆ๋๋ฆฌํฐ & ํจํด ์ค๋ช "): | |
comp_fig = m_micro.plot_components(fc_micro) | |
st.pyplot(comp_fig) | |
month_season = (fc_micro[["ds", "yearly"]] | |
.assign(month=lambda d: d.ds.dt.month) | |
.groupby("month")["yearly"].mean()) | |
st.markdown( | |
f"**์ฐ๊ฐ ํผํฌ ์:** {int(month_season.idxmax())}์\n\n" | |
f"**์ฐ๊ฐ ์ ์ ์:** {int(month_season.idxmin())}์\n\n" | |
f"**์ฐ๊ฐ ๋ณ๋ํญ:** {month_season.max() - month_season.min():.1f}") | |
# Correlation heatmap | |
st.subheader("๐งฎ ํ๋ชฉ ๊ฐ ์๊ด๊ด๊ณ") | |
monthly_pivot = (raw_df.assign(month=lambda d: d.date.dt.to_period("M")) | |
.groupby(["month", "item"], as_index=False)["price"].mean() | |
.pivot(index="month", columns="item", values="price")) | |
corr = monthly_pivot.corr() | |
mask = np.triu(np.ones_like(corr, dtype=bool)) | |
fig, ax = plt.subplots(figsize=(12, 10)) | |
sns.heatmap(corr, mask=mask, cmap="RdBu_r", center=0, linewidths=.5, ax=ax) | |
st.pyplot(fig) | |
st.info("๋นจ๊ฐ ์์ญ: ๊ฐ๊ฒฉ ๋์กฐํ / ํ๋ ์์ญ: ๋์ฒด์ฌ ๊ฐ๋ฅ์ฑ.") | |
# Volatility Chart | |
st.subheader("๐ 30์ผ ์ด๋ ํ์คํธ์ฐจ (๊ฐ๊ฒฉ ๋ณ๋์ฑ)") | |
vol = item_df.set_index("date")["price"].rolling(30).std().dropna().reset_index() | |
fig_vol = px.area(vol, x="date", y="price", title="Rolling 30D Std Dev") | |
st.plotly_chart(fig_vol, use_container_width=True) | |
st.caption("๋ฐ์ดํฐ: domae-202503.parquet ยท Prophet ์์ธก ยท Streamlit ๋์๋ณด๋") | |