【完全手順】Streamlitで株価ダッシュボード自作!SBI証券での取引をサポート

Python実装・コード

※本記事のコードや情報は執筆時点の仕様に基づいています。投資は自己責任であり、必ずデモ環境や少額資金でテストした上で運用してください。

Pythonでテクニカル指標を計算できるようになっても、毎回ターミナルでスクリプトを実行し、数値の羅列を目視で確認するのは非効率です。

ブラウザ上でチャートや指標をリアルタイムに確認できるダッシュボードがあれば、SBI証券での手動取引の判断スピードと精度が大幅に向上します。

しかし、多くの個人投資家が「Webアプリ開発」という言葉にハードルの高さを感じて、ダッシュボード構築を諦めています。

HTML・CSS・JavaScriptの知識が必要なのではないか、サーバー構築が面倒なのではないか、という不安があるためです。

この記事では、Streamlitを使ってPythonコードだけで株価ダッシュボードを構築する方法を解説します。yfinanceで取得した実データをもとに、ローソク足チャート・移動平均線・RSI・出来高を1画面で確認できるダッシュボードを作ります。

すべてコピペでそのまま動作する実装に限定していますので、自分の監視銘柄や指標に差し替えて応用してください。

Streamlitが株価ダッシュボードに最適な3つの理由

ダッシュボード構築にはDash、Flask、Djangoなど複数の選択肢がありますが、個人投資家の用途にはStreamlitが最も適しています。

ここではその理由を明確にします。

Pythonだけで完結する

Streamlitは、HTML・CSS・JavaScriptを一切書かずにWebアプリを構築できるフレームワークです。

st.line_chart()st.dataframe() のように、Python関数を呼ぶだけでUIコンポーネントが生成されます。

普段Pythonでデータ分析をしている環境がそのままWebアプリになるため、新しい言語の学習コストがゼロです。

サイドバーとウィジェットで対話的に操作できる

Streamlitにはサイドバー、スライダー、セレクトボックスなどのウィジェットが標準搭載されています。

銘柄コードの切り替え、表示期間の変更、テクニカル指標の切り替えといった操作を、コード数行で実装できます。

SBI証券で取引する前に「この銘柄、この期間、この指標ではどうか」をブラウザ上で素早く切り替えて確認できる点が実用上の最大のメリットです。

ローカル環境で即座に起動できる

Streamlitは streamlit run app.py というコマンド1つでローカルサーバーが立ち上がり、ブラウザが自動で開きます。

サーバー構築やデプロイの設定は不要です。

自分のPC上で動かすだけなら、インストールから初回起動まで5分もかかりません。

【コピペOK】株価ダッシュボードの完全実装コード

ここからは実際のコードを提示します。

yfinanceで株価データを取得し、ローソク足チャート・移動平均線(5日・25日・75日)・RSI・出来高を1画面に表示するダッシュボードを構築します。

事前準備:ライブラリのインストール


pip install streamlit yfinance plotly pandas

【コピペOK】ダッシュボード本体コード(app.py)


import streamlit as st
import yfinance as yf
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# ==============================
# 設定エリア
# ==============================
DEFAULT_SYMBOL = "7203.T"  # デフォルト銘柄(トヨタ自動車)
DEFAULT_PERIOD = "1y"      # デフォルト表示期間
RSI_WINDOW = 14            # RSI計算期間

PERIOD_OPTIONS = {
    "1ヶ月": "1mo",
    "3ヶ月": "3mo",
    "6ヶ月": "6mo",
    "1年": "1y",
    "2年": "2y",
    "5年": "5y",
}

MA_WINDOWS = [5, 25, 75]  # 移動平均線の期間リスト

# ==============================
# ページ設定
# ==============================
st.set_page_config(
    page_title="株価ダッシュボード",
    layout="wide",
    initial_sidebar_state="expanded",
)

# ==============================
# データ取得
# ==============================
@st.cache_data(ttl=300)  # 5分間キャッシュ
def fetch_data(symbol: str, period: str) -> pd.DataFrame:
    ""yfinanceから株価データを取得します。""
    ticker = yf.Ticker(symbol)
    df = ticker.history(period=period)
    if df.empty:
        return pd.DataFrame()
    return df

# ==============================
# RSI計算(Wilder方式)
# ==============================
def calc_rsi(series: pd.Series, window: int = 14) -> pd.Series:
    ""RSIをWilder方式で計算します。""
    delta = series.diff()
    gain = delta.where(delta > 0, 0.0)
    loss = (-delta).where(delta < 0, 0.0)
    avg_gain = gain.ewm(alpha=1 / window, min_periods=window, adjust=False).mean()
    avg_loss = loss.ewm(alpha=1 / window, min_periods=window, adjust=False).mean()
    rs = avg_gain / avg_loss
    return 100 - (100 / (1 + rs))

# ==============================
# 移動平均線の追加
# ==============================
def add_moving_averages(df: pd.DataFrame, windows: list) -> pd.DataFrame:
    ""指定期間の移動平均線をDataFrameに追加します。""
    df = df.copy()
    for w in windows:
        df[f"MA{w}"] = df["Close"].rolling(window=w).mean()
    return df

# ==============================
# チャート描画
# ==============================
def create_dashboard_chart(
    df: pd.DataFrame,
    symbol: str,
    show_ma: bool,
    show_rsi: bool,
    show_volume: bool,
) -> go.Figure:
    ""ローソク足+選択した指標を含むチャートを生成します。""

    # サブプロット数の決定
    row_count = 1
    row_heights = [0.5]
    if show_rsi:
        row_count += 1
        row_heights.append(0.25)
    if show_volume:
        row_count += 1
        row_heights.append(0.25)

    fig = make_subplots(
        rows=row_count,
        cols=1,
        shared_xaxes=True,
        vertical_spacing=0.04,
        row_heights=row_heights,
    )

    # --- ローソク足 ---
    fig.add_trace(
        go.Candlestick(
            x=df.index,
            open=df["Open"],
            high=df["High"],
            low=df["Low"],
            close=df["Close"],
            name="OHLC",
            increasing_line_color="#26A69A",
            decreasing_line_color="#EF5350",
        ),
        row=1,
        col=1,
    )

    # --- 移動平均線 ---
    if show_ma:
        ma_colors = {"MA5": "#FF9800", "MA25": "#2196F3", "MA75": "#9C27B0"}
        for col_name, color in ma_colors.items():
            if col_name in df.columns:
                fig.add_trace(
                    go.Scatter(
                        x=df.index,
                        y=df[col_name],
                        mode="lines",
                        name=col_name,
                        line=dict(width=1.2, color=color),
                    ),
                    row=1,
                    col=1,
                )

    current_row = 2

    # --- RSI ---
    if show_rsi:
        fig.add_trace(
            go.Scatter(
                x=df.index,
                y=df["RSI"],
                mode="lines",
                name="RSI(14)",
                line=dict(width=1.2, color="#FF5722"),
            ),
            row=current_row,
            col=1,
        )
        # 買われすぎ・売られすぎライン
        fig.add_hline(y=70, line_dash="dash", line_color="gray", opacity=0.5, row=current_row, col=1)
        fig.add_hline(y=30, line_dash="dash", line_color="gray", opacity=0.5, row=current_row, col=1)
        fig.update_yaxes(title_text="RSI", row=current_row, col=1)
        current_row += 1

    # --- 出来高 ---
    if show_volume:
        colors = [
            "#26A69A" if c >= o else "#EF5350"
            for c, o in zip(df["Close"], df["Open"])
        ]
        fig.add_trace(
            go.Bar(
                x=df.index,
                y=df["Volume"],
                name="出来高",
                marker_color=colors,
                opacity=0.7,
            ),
            row=current_row,
            col=1,
        )
        fig.update_yaxes(title_text="出来高", row=current_row, col=1)

    # --- レイアウト設定 ---
    fig.update_layout(
        title=f"{symbol} 株価ダッシュボード",
        xaxis_rangeslider_visible=False,
        height=200 + row_count * 250,
        template="plotly_white",
        legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
        margin=dict(l=60, r=30, t=80, b=40),
    )
    fig.update_yaxes(title_text="株価 (JPY)", row=1, col=1)

    return fig

# ==============================
# サマリー指標の計算
# ==============================
def calc_summary(df: pd.DataFrame) -> dict:
    ""直近の株価サマリーを計算します。""
    if df.empty or len(df) < 2:
        return {}

    latest = df.iloc[-1]
    prev = df.iloc[-2]
    change = latest["Close"] - prev["Close"]
    change_pct = (change / prev["Close"]) * 100

    high_max = df["High"].max()
    low_min = df["Low"].min()

    summary = {
        "終値": f'¥{latest["Close"]:,.1f}',
        "前日比": f'¥{change:+,.1f} ({change_pct:+.2f}%)',
        "期間高値": f'¥{high_max:,.1f}',
        "期間安値": f'¥{low_min:,.1f}',
    }

    if "RSI" in df.columns and not pd.isna(df["RSI"].iloc[-1]):
        summary["RSI(14)"] = f'{df["RSI"].iloc[-1]:.1f}'

    return summary

# ==============================
# サイドバー
# ==============================
st.sidebar.title("⚙ 設定")

symbol_input = st.sidebar.text_input(
    "銘柄コード(例: 7203.T)",
    value=DEFAULT_SYMBOL,
)

period_label = st.sidebar.selectbox(
    "表示期間",
    options=list(PERIOD_OPTIONS.keys()),
    index=3,  # デフォルト「1年」
)
period_value = PERIOD_OPTIONS[period_label]

st.sidebar.markdown("---")
st.sidebar.subheader("📊 表示する指標")
show_ma = st.sidebar.checkbox("移動平均線(5/25/75日)", value=True)
show_rsi = st.sidebar.checkbox("RSI(14日)", value=True)
show_volume = st.sidebar.checkbox("出来高", value=True)

# ==============================
# メイン画面
# ==============================
st.title("📈 株価ダッシュボード")

# データ取得
df = fetch_data(symbol_input, period_value)

if df.empty:
    st.error(f"⚠ {symbol_input} のデータを取得できませんでした。銘柄コードを確認してください。")
    st.stop()

# 指標の追加
df = add_moving_averages(df, MA_WINDOWS)
df["RSI"] = calc_rsi(df["Close"], RSI_WINDOW)

# サマリー表示
summary = calc_summary(df)
if summary:
    cols = st.columns(len(summary))
    for col, (label, value) in zip(cols, summary.items()):
        col.metric(label=label, value=value)

st.markdown("---")

# チャート表示
fig = create_dashboard_chart(df, symbol_input, show_ma, show_rsi, show_volume)
st.plotly_chart(fig, use_container_width=True)

# 直近データテーブル
with st.expander("📋 直近20日間の株価データ"):
    display_cols = ["Open", "High", "Low", "Close", "Volume"]
    if show_ma:
        display_cols += [f"MA{w}" for w in MA_WINDOWS]
    if show_rsi:
        display_cols.append("RSI")
    st.dataframe(
        df[display_cols].tail(20).sort_index(ascending=False).style.format(precision=1),
        use_container_width=True,
    )

# フッター
st.sidebar.markdown("---")
st.sidebar.caption("データ取得元: Yahoo Finance(yfinance)")
st.sidebar.caption("※表示データは約5分間キャッシュされます")

起動方法

ファイルを app.py として保存し、以下のコマンドで起動してください。


streamlit run app.py

ブラウザが自動で開き、ダッシュボードが表示されます。

コードの処理フロー解説

上記のコードは、以下の6ステップで構成されています。

* ページ設定:Streamlitのレイアウトとタイトルを設定します

* データ取得:yfinanceで指定銘柄・期間の日足データを取得し、5分間キャッシュします

* 指標計算:移動平均線(5日・25日・75日)とRSI(14日)を算出します

* サマリー表示:終値・前日比・期間高値安値・RSIをメトリクスカードで表示します

* チャート描画:Plotlyでローソク足・移動平均線・RSI・出来高をサブプロット構成で描画します

* データテーブル:直近20日間の数値データをエキスパンダー内で確認できます

サイドバーの銘柄コードや表示期間を変更すると、画面が自動で再描画されます。SBI証券で注文を出す前に、複数銘柄を素早く切り替えて確認する使い方に最適です。

ダッシュボードのカスタマイズ方法

基本のダッシュボードが動いたら、自分の取引スタイルに合わせてカスタマイズしましょう。

ここでは、よく使われる3つのカスタマイズパターンを解説します。

監視銘柄のプリセット化

毎回銘柄コードを手入力するのは手間です。

よく監視する銘柄をプリセットとして登録しておけば、セレクトボックスで切り替えるだけで済みます。

サイドバーの銘柄入力部分を以下のように差し替えてください。


WATCHLIST = {
    "トヨタ自動車": "7203.T",
    "ソニーグループ": "6758.T",
    "任天堂": "7974.T",
    "キーエンス": "6861.T",
    "日経225 ETF": "1321.T",
}

selected_name = st.sidebar.selectbox(
    "監視銘柄",
    options=list(WATCHLIST.keys()),
)
symbol_input = WATCHLIST[selected_name]

テクニカル指標の追加

現在のダッシュボードはRSIのみですが、ボリンジャーバンドやMACDを追加したい場合は、指標計算関数を追加し、create_dashboard_chart() 内にトレースを追加する構成で対応できます。

指標ごとにサイドバーのチェックボックスを用意し、ユーザーが表示・非表示を切り替えられるようにしてください。

表示レイアウトの調整

st.set_page_config(layout="wide") をデフォルトにしていますが、小さいモニターでは layout="centered" の方が見やすい場合があります。

チャートの高さは create_dashboard_chart() 内の height=200 + row_count * 250 で制御しています。モニターサイズに合わせて数値を調整してください。

【コピペOK】売買シグナル表示を追加した拡張版

ダッシュボードにRSIベースの売買シグナル(RSI≦30で買い、RSI≧70で売り)をチャート上に表示する機能を追加します。

以下の関数をコードに追加し、チャート描画部分に組み込んでください。

【コピペOK】シグナル生成+チャート追加コード


# ==============================
# 売買シグナル生成
# ==============================
def generate_signals(df: pd.DataFrame, buy_th: int = 30, sell_th: int = 70) -> pd.DataFrame:
    ""RSIに基づく売買シグナルを生成します。""
    df = df.copy()
    df["Buy_Signal"] = False
    df["Sell_Signal"] = False

    position = 0  # 0=ノーポジ, 1=保有中

    for i in range(1, len(df)):
        rsi_val = df["RSI"].iloc[i]
        if pd.isna(rsi_val):
            continue

        if rsi_val <= buy_th and position == 0:
            df.iloc[i, df.columns.get_loc("Buy_Signal")] = True
            position = 1
        elif rsi_val >= sell_th and position == 1:
            df.iloc[i, df.columns.get_loc("Sell_Signal")] = True
            position = 0

    return df

シグナル生成後、チャートの描画関数内(ローソク足の直後)に以下を追加してください。


# --- 買いシグナルマーカー ---
buy_signals = df[df["Buy_Signal"]]
if not buy_signals.empty:
fig.add_trace(
go.Scatter(
x=buy_signals.index,
y=buy_signals["Low"] * 0.98,
mode="markers",
name="買いシグナル",
marker=dict(symbol="triangle-up", size=12, color="#26A69A"),
),
row=1,
col=1,
)

# --- 売りシグナルマーカー ---
sell_signals = df[df["Sell_Signal"]]
if not sell_signals.empty:
fig.add_trace(
go.Scatter(
x=sell_signals.index,
y=sell_signals["High"] * 1.02,
mode="markers",
name="売りシグナル",
marker=dict(symbol="triangle-down", size=12, color="#EF5350"),
),
row=1,
col=1,
)

タイトルとURLをコピーしました