ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

๋ฐ˜์‘ํ˜•

๐Ÿ“Š AI ํ€€ํŠธ ํ†ตํ•ฉ ์šด์šฉ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ์ถ• – Streamlit์œผ๋กœ ์‹ค์‹œ๊ฐ„ ํŠธ๋ ˆ์ด๋”ฉ·๋ฆฌ์Šคํฌ·์„ฑ๊ณผ๋ฅผ ํ•œ๋ˆˆ์—

— “AI๊ฐ€ ํˆฌ์žํ•˜๊ณ , ๋‚˜๋Š” ๊ฒฐ๊ณผ๋งŒ ํ™•์ธํ•œ๋‹ค.” ์™„์ „ ์ž๋™ํ™”๋œ ํ€€ํŠธ ๋Œ€์‹œ๋ณด๋“œ ๋งŒ๋“ค๊ธฐ

์ด์ œ ์šฐ๋ฆฌ ์‹œ์Šคํ…œ์€ AI๊ฐ€ ์Šค์Šค๋กœ ํ•™์Šตํ•˜๊ณ , ์˜ˆ์ธกํ•˜๊ณ , ํˆฌ์žํ•˜๋Š” ๋‹จ๊ณ„๊นŒ์ง€ ์™”์Šต๋‹ˆ๋‹ค.
์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ๊ทธ ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ํ•œ ํ™”๋ฉด์—์„œ ์ง๊ด€์ ์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ์ˆ˜ ์žˆ๋Š” ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋ด…๋‹ˆ๋‹ค.

์ฆ‰, ์ง€๊ธˆ๊นŒ์ง€ ๋งŒ๋“ 

  • ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘
  • ๋ชจ๋ธ ์˜ˆ์ธก
  • ๋ฆฌ์Šคํฌ ๊ด€๋ฆฌ
  • ์ž์‚ฐ๋ฐฐ๋ถ„
  • ๋ฐฑํ…Œ์ŠคํŠธ ๋ฐ ์‹ค๊ฑฐ๋ž˜ ๊ฒฐ๊ณผ
    ์ด ๋ชจ๋“  ๊ฑธ ํ†ตํ•ฉํ•˜๋Š” AI ์šด์šฉ ์ฝ˜์†”์„ ์™„์„ฑํ•ฉ๋‹ˆ๋‹ค.

๐ŸŽฏ ๋ชฉํ‘œ

“ํ•˜๋‚˜์˜ Streamlit ํ™”๋ฉด์—์„œ
โ‘  AI ๋ชจ๋ธ ์ƒํƒœ,
โ‘ก ์‹ค์‹œ๊ฐ„ ์‹œ์žฅ ๋ฐ์ดํ„ฐ,
โ‘ข ํฌํŠธํด๋ฆฌ์˜ค ๋น„์ค‘,
โ‘ฃ ๋ˆ„์  ์ˆ˜์ต๋ฅ ,
โ‘ค ๋ฆฌ์Šคํฌ ์ง€ํ‘œ๋ฅผ
์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•œ๋‹ค.”


โš™๏ธ 1๏ธโƒฃ Streamlit ํ™˜๊ฒฝ ๊ตฌ์„ฑ

pip install streamlit plotly requests pandas redis sqlalchemy
  • plotly: ์‹ค์‹œ๊ฐ„ ์ฐจํŠธ ์‹œ๊ฐํ™”
  • redis: ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์บ์‹œ
  • sqlalchemy: PostgreSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ

๐Ÿงฑ 2๏ธโƒฃ ์ „์ฒด ๋ ˆ์ด์•„์›ƒ ๊ตฌ์„ฑ

import streamlit as st
import pandas as pd
import plotly.express as px
import requests, redis, json
from sqlalchemy import create_engine

st.set_page_config(page_title="AI Quant Dashboard", layout="wide")
st.title("๐Ÿ“Š AI Quant Investment Control Panel")

r = redis.Redis()
engine = create_engine("postgresql://quant_user:quant_pass@localhost:5432/quantdb")

col1, col2 = st.columns([2, 1])

๐Ÿงฉ 3๏ธโƒฃ ์‹ค์‹œ๊ฐ„ ์‹œ์žฅ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ

st.sidebar.header("๐Ÿ“ˆ ์‹ค์‹œ๊ฐ„ ์‹œ์„ธ")
live_quotes = {k.decode(): float(v) for k, v in r.hgetall("live_quotes").items()}
df_quotes = pd.DataFrame(list(live_quotes.items()), columns=["Symbol", "Price"])
st.sidebar.table(df_quotes)

Redis์— ์Œ“์ด๋Š” ์‹ค์‹œ๊ฐ„ WebSocket ์‹œ์„ธ(live_quotes)๋ฅผ ๊ทธ๋Œ€๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ’ฐ 4๏ธโƒฃ ํฌํŠธํด๋ฆฌ์˜ค ํ˜„ํ™ฉ

๋ฐ˜์‘ํ˜•
col1.subheader("๐Ÿ’ผ Portfolio Overview")
df_port = pd.read_sql("SELECT * FROM portfolio_snapshot", engine)
col1.dataframe(df_port[["ticker", "quantity", "entry_price", "current_price", "current_pnl"]])

โœ… ์ฃผ์š” ์ง€ํ‘œ

  • current_pnl: ํ˜„์žฌ ์†์ต
  • entry_price: ๋งค์ž…๊ฐ€
  • quantity: ๋ณด์œ  ์ˆ˜๋Ÿ‰

๐Ÿ“ˆ 5๏ธโƒฃ ๋ˆ„์  ์ˆ˜์ต๋ฅ  ์ฐจํŠธ

cum_ret = df_port.groupby("date")["current_pnl"].sum().cumsum()
fig = px.line(cum_ret, title="๐Ÿ“ˆ Cumulative P&L", labels={"value": "PnL", "index": "Date"})
col1.plotly_chart(fig, use_container_width=True)

์ˆ˜์ต๋ฅ ์ด ์‹ค์ œ ๊ฑฐ๋ž˜์™€ ๋ฐฑํ…Œ์ŠคํŠธ ์–‘์ชฝ ๋ฐ์ดํ„ฐ๋ฅผ ํ†ตํ•ฉํ•ด ์ž๋™ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค.


๐Ÿง  6๏ธโƒฃ AI ๋ชจ๋ธ ์ƒํƒœ

col2.subheader("๐Ÿค– AI Model Status")
mlflow_api = "http://localhost:5001/api/2.0/mlflow/experiments/list"
mlflow_data = requests.get(mlflow_api).json()

exp_df = pd.DataFrame(mlflow_data["experiments"])
col2.metric("ํ™œ์„ฑ ๋ชจ๋ธ ์ˆ˜", len(exp_df))
col2.json(exp_df[["name", "lifecycle_stage", "artifact_location"]].to_dict("records")[:3])
  • MLflow์™€ ์—ฐ๊ฒฐ๋˜์–ด ๋ชจ๋ธ ๋ฒ„์ „, ์ƒํƒœ(Production/Staging)๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
  • ์ตœ์‹  ๋ชจ๋ธ์ด ์ž๋™ ๋ฐฐํฌ๋˜๋ฉด ์ด ํ™”๋ฉด์—์„œ ์ฆ‰์‹œ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค.

๐Ÿงญ 7๏ธโƒฃ ๋ฆฌ์Šคํฌ ์ง€ํ‘œ ๋ชจ๋‹ˆํ„ฐ๋ง

st.subheader("๐Ÿงญ Risk Metrics")
risk_data = json.loads(r.get("risk_status"))
c1, c2, c3 = st.columns(3)
c1.metric("VaR (99%)", f"{risk_data['VaR']*100:.2f}%")
c2.metric("CVaR (99%)", f"{risk_data['CVaR']*100:.2f}%")
c3.metric("MDD", f"{risk_data['MDD']*100:.2f}%")

if risk_data["Status"].startswith("โš ๏ธ"):
    st.warning("๋ฆฌ์Šคํฌ ์ฃผ์˜ ๊ตฌ๊ฐ„! ํ˜„๊ธˆ ๋น„์ค‘ ํ™•๋Œ€ ๊ถŒ์žฅ โš ๏ธ")
else:
    st.success("์•ˆ์ • ๊ตฌ๊ฐ„์ž…๋‹ˆ๋‹ค โœ…")

Flask API์—์„œ ์‹ค์‹œ๊ฐ„ ๋ฆฌ์Šคํฌ ์ƒํƒœ๋ฅผ ๋ถˆ๋Ÿฌ์™€ ๊ฒฝ๊ณ  ์—ฌ๋ถ€๋ฅผ ์ž๋™ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.


๐Ÿช™ 8๏ธโƒฃ ์ž์‚ฐ ๋ฐฐ๋ถ„ ๋ฐ ๋กœํ…Œ์ด์…˜ ์ƒํƒœ

st.subheader("๐ŸŒ Global Asset Allocation")
alloc_api = "http://localhost:5000/allocation"
alloc = requests.get(alloc_api).json()
st.bar_chart(pd.Series(alloc))
  • AI ์ž์‚ฐ๋ฐฐ๋ถ„ ๋ชจ๋ธ(๋ฉ€ํ‹ฐ์—์…‹/๋กœํ…Œ์ด์…˜)์˜ ์ตœ์‹  ์ถ”์ฒœ ๋น„์ค‘ ํ‘œ์‹œ
  • ๋ง‰๋Œ€๊ทธ๋ž˜ํ”„ ํ˜•ํƒœ๋กœ ์‹ค์‹œ๊ฐ„ ๊ฐฑ์‹ 

๐Ÿ”” 9๏ธโƒฃ ์•Œ๋ฆผ ๋ฐ ๋ฆฌํฌํŠธ ๋งํฌ

st.subheader("๐Ÿ“ฐ Notifications")
st.info("๐Ÿ“… ๋‹ค์Œ ๋ฆฌ๋ฐธ๋Ÿฐ์‹ฑ ์ผ์ •: 2025-12-01")
st.write("๐Ÿ“„ [์ตœ๊ทผ ํˆฌ์ž ๋ฆฌํฌํŠธ ๋‹ค์šด๋กœ๋“œ](./reports/quant_202511.pdf)")

Airflow DAG๊ณผ ์—ฐ๋™๋˜์–ด ๋ฆฌ๋ฐธ๋Ÿฐ์‹ฑ ์Šค์ผ€์ค„ ๋ฐ ๋ฆฌํฌํŠธ ๋งํฌ ์ž๋™ ๊ฐฑ์‹ .


๐Ÿง  10๏ธโƒฃ ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ

import time
st_autorefresh = st.sidebar.checkbox("์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ", value=True)
if st_autorefresh:
    st.experimental_rerun()
    time.sleep(30)

๋Œ€์‹œ๋ณด๋“œ๊ฐ€ 30์ดˆ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒˆ๋กœ ๋ถˆ๋Ÿฌ์™€
๊ฑฐ์˜ ์‹ค์‹œ๊ฐ„์— ๊ฐ€๊นŒ์šด ์šด์˜ ๋ชจ๋‹ˆํ„ฐ๋ง์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ“ˆ ๊ฒฐ๊ณผ – AI ํ€€ํŠธ ์šด์šฉ ์ฝ˜์†”

โœ… Streamlit ์‹ค์‹œ๊ฐ„ ๋Œ€์‹œ๋ณด๋“œ ์™„์„ฑ

  • ์‹ค์‹œ๊ฐ„ ์‹œ์„ธ
  • ํฌํŠธํด๋ฆฌ์˜ค ์†์ต
  • AI ๋ชจ๋ธ ์ƒํƒœ
  • VaR/CVaR ๋ฆฌ์Šคํฌ
  • ์ž์‚ฐ๋ฐฐ๋ถ„ ์ฐจํŠธ
  • ๋ฆฌํฌํŠธ ๋‹ค์šด๋กœ๋“œ

“AI๊ฐ€ ํˆฌ์žํ•˜๊ณ , ์‚ฌ๋žŒ์€ ๋ชจ๋‹ˆํ„ฐ๋ง๋งŒ ํ•˜๋Š” ์™„์ „ ์ž๋™ํ™” ์‹œ์Šคํ…œ”
์ด์ œ ์šด์šฉ์‚ฌ ์ˆ˜์ค€์˜ ์ฝ˜์†”์„ ๊ฐœ์ธ ๊ฐœ๋ฐœ์ž๋„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ“˜ ๋‹ค์Œ ๊ธ€ ์˜ˆ๊ณ 

๋‹ค์Œ ํŽธ์—์„œ๋Š” **“AI ํ€€ํŠธ ์‹œ์Šคํ…œ ๋ฐฐํฌ – Docker Compose + Nginx Reverse Proxy + SSL ์„ค์ •๊นŒ์ง€”**๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค.
๋กœ์ปฌ์—์„œ ๊ตฌ์ถ•ํ•œ AI ์šด์šฉ ์‹œ์Šคํ…œ์„
ํด๋ผ์šฐ๋“œ ์„œ๋ฒ„์— ์•ˆ์ „ํ•˜๊ฒŒ ๋ฐฐํฌํ•˜๊ณ  ์™ธ๋ถ€ ์ ‘์† ๊ฐ€๋Šฅํ•œ ์‹ค์ „ ํ™˜๊ฒฝ์œผ๋กœ ๋งŒ๋“œ๋Š” ๋‹จ๊ณ„์ž…๋‹ˆ๋‹ค.


 

AIํ€€ํŠธ,Streamlit,ํŠธ๋ ˆ์ด๋”ฉ๋Œ€์‹œ๋ณด๋“œ,๋ฆฌ์Šคํฌ๊ด€๋ฆฌ,ํฌํŠธํด๋ฆฌ์˜ค์‹œ๊ฐํ™”,MLflow,Redis,PostgreSQL,์‹ค์‹œ๊ฐ„ํˆฌ์ž,ํ€€ํŠธ์šด์šฉ


 

โ€ป ์ด ํฌ์ŠคํŒ…์€ ์ฟ ํŒก ํŒŒํŠธ๋„ˆ์Šค ํ™œ๋™์˜ ์ผํ™˜์œผ๋กœ, ์ด์— ๋”ฐ๋ฅธ ์ผ์ •์•ก์˜ ์ˆ˜์ˆ˜๋ฃŒ๋ฅผ ์ œ๊ณต๋ฐ›์Šต๋‹ˆ๋‹ค.
๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
Total
Today
Yesterday
๋งํฌ
ยซ   2026/02   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
๊ธ€ ๋ณด๊ด€ํ•จ
๋ฐ˜์‘ํ˜•