📑

朝から株野郎?計画2

に公開

前回の記事

https://zenn.dev/rucco/articles/a0e803662d8d46

DBは何を使う?

ふうこさんに相談したところ、
Androidアプリと連携するのであれば、SQLiteかPostgreSQLが鉄板とのこと。

DB種類 特徴 向いてる用途 ふうこ評価
SQLite ファイル型DB。導入が超カンタン。 小規模アプリ、ローカル開発 ⭐️⭐️⭐️⭐️(最初に◎)
PostgreSQL 本格的なRDBMS。拡張性高い。 サーバーに置いて複数アプリからアクセス ⭐️⭐️⭐️⭐️⭐️(中〜長期で最強)

よし、まずは簡単そうなSQLiteにしよう。
公開とかが見えてきたらPostgreSQLに切り替えることを検討ということで!!!

データの流れ方を整理すると、こんな感じ。
yFinanceから株データを取得→DB保存→データ加工→FastAPI→Android

テーブル設計する

テーブルは以下を検討。

company_info テーブル(企業指標など:上書き保存)

カラム名 説明
id INTEGER 自動採番・主キー
symbol TEXT 銘柄コード(例: 7203.T)
date TEXT データ取得日(例: 2025-07-06)
market_cap REAL 時価総額
enterprise_value REAL 企業価値(Enterprise Value)
trailing_pe REAL PER(株価収益率)
forward_pe REAL 予想PER
peg_ratio REAL PEGレシオ
price_to_book REAL PBR(株価純資産倍率)
earnings_quarterly_growth REAL 四半期利益成長率(前年比)
dividend REAL 年間配当額(1株あたり)
dividend_yield REAL 配当利回り(%)
payout_ratio REAL 配当性向(%)
last_dividend_date TEXT 直近の配当支払日
ex_dividend_date TEXT 権利落ち日

stock_prices テーブル(株価履歴:日々追加)

カラム名 説明
id INTEGER 自動採番・主キー
symbol TEXT 銘柄コード(例: 7203.T)
date TEXT 取引日(例: 2025-07-06)
open REAL 始値
high REAL 高値
low REAL 安値
close REAL 終値
volume INTEGER 出来高

DBセットアップする!

Pythonでinit_db.pyを新規作成。
以下コードを書いて、F5で実行。

import sqlite3
def init_db():
    # データベースに接続(なければ作成)
    conn = sqlite3.connect('stocks.db')
    cursor = conn.cursor()

    # 企業情報テーブルの作成
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS company_info (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            symbol TEXT NOT NULL,
            date TEXT NOT NULL,
            market_cap REAL,
            enterprise_value REAL,
            trailing_pe REAL,
            forward_pe REAL,
            peg_ratio REAL,
            price_to_book REAL,
            earnings_quarterly_growth REAL,
            dividend REAL,
            dividend_yield REAL,
            payout_ratio REAL,
            last_dividend_date TEXT,
            ex_dividend_date TEXT,
            UNIQUE (symbol) ON CONFLICT REPLACE
        )
    ''')
    # 株価データテーブルの作成
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS stock_prices (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            symbol TEXT NOT NULL,
            date TEXT NOT NULL,
            open REAL,
            high REAL,
            low REAL,
            close REAL,
            volume INTEGER,
            UNIQUE (symbol, date) ON CONFLICT REPLACE
        )
    ''')

    # 株価データのインデックスを作成
    cursor.execute('''
        CREATE INDEX IF NOT EXISTS idx_stock_prices_symbol_date ON stock_prices (symbol, date)
    ''')

    # 企業情報のインデックスを作成
    cursor.execute('''
        CREATE INDEX IF NOT EXISTS idx_company_info_symbol_date ON company_info (symbol, date)
    ''')

    # 株価データのユニーク制約を追加
    cursor.execute('''
        CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_stock_prices ON stock_prices (symbol, date)
    ''')

    # 変更を保存して接続を閉じる
    conn.commit()
    conn.close()

if __name__ == "__main__":
    init_db()
    print("データベースの初期化が完了しました。")

エラーがなければinit_db.pyと同じフォルダに「stocks.db」ができる。
さっそく中身を確認!!!

・・・と思ったら、見れなかった😮

私のVSCodeにはDBの中を見るための拡張機能がなかったのね。
拡張機能を入れよう。
SQLiteで検索したらめっちゃいっぱい出てきた。
インストール数が多いのに、高評価なSQLiteViewerにしよう。

やっとDBの中を確認できました😅
私が作ったテーブル2個と、SQLiteが勝手に作ったテーブル1つありますね。
内容については想定通りなので、よし!

データ入れてみるか

データを突っ込まないと話にならないので、アメリカと日本から1社ずつ選択して
入れてみることにする。
アメリカはAAPL(アップル)、日本は7203.T(トヨタ自動車)
PEGが取れないことが分かったので、ありものデータで無理やり計算。
(そこまでしてPEGいるのかな💦後で削除するかも。)
コパイロットが空気を読んでコードを先に書いてくれるから、SQL書くのもめっちゃ楽だった。
自分で書くとスペルミスで余裕で時間つぶすので、本当AIってすごいなって思った。

企業情報テーブル編

import sqlite3
import yfinance as yf
from datetime import datetime, timedelta

def update_company_info(symbol:str):
    # yfinanceを使用して指定銘柄の情報を取得
    ticker = yf.Ticker(symbol)
    info = ticker.info

    # 四半期成長率を使ってPEG計算(PEGが取れないので)
    earnings_quarterly_growth = info.get('earningsQuarterlyGrowth', None)
    if earnings_quarterly_growth is not None:
        # 四半期成長率を年率換算
        eps_growth_rate = (1 + earnings_quarterly_growth) ** 4 - 1
    else:
        eps_growth_rate = None

    # PEG比率の計算
    trailing_pe = info.get('trailingPE', None)
    if eps_growth_rate and eps_growth_rate != 0:
        peg_ratio = trailing_pe / eps_growth_rate
    else:
        peg_ratio = None

    # 今日の日付を取得
    today = datetime.now().strftime("%Y-%m-%d")

    # データベースに接続
    conn = sqlite3.connect('stocks.db')
    cursor = conn.cursor()

    # 企業情報の準備
    company_info = {
        "symbol": symbol,
        "date": today,
        "market_cap": info.get('marketCap', None),
        "enterprise_value": info.get('enterpriseValue', None),
        "trailing_pe": info.get('trailingPE', None),
        "forward_pe": info.get('forwardPE', None),
        "peg_ratio": peg_ratio,
        "price_to_book": info.get('priceToBook', None),
        "earnings_quarterly_growth": info.get('earningsQuarterlyGrowth', None),
        "dividend": info.get('dividendRate', None),
        "dividend_yield": info.get('dividendYield', None),
        "payout_ratio": info.get('payoutRatio', None),
        "last_dividend_date": info.get('lastDividendDate', None),
        "ex_dividend_date": info.get('exDividendDate', None)
    }

    # 日付の変換
    if company_info["last_dividend_date"]:
        company_info["last_dividend_date"] = datetime.fromtimestamp(company_info["last_dividend_date"]).strftime("%Y-%m-%d")
    if company_info["ex_dividend_date"]:
        company_info["ex_dividend_date"] = datetime.fromtimestamp(company_info["ex_dividend_date"]).strftime("%Y-%m-%d")


    # 企業情報をデータベースに挿入または更新
    cursor.execute('''
        INSERT INTO company_info (symbol, date, market_cap, enterprise_value, trailing_pe, forward_pe,
                                  peg_ratio, price_to_book, earnings_quarterly_growth, dividend,
                                  dividend_yield, payout_ratio, last_dividend_date, ex_dividend_date)
        VALUES (:symbol, :date, :market_cap, :enterprise_value, :trailing_pe, :forward_pe,
                :peg_ratio, :price_to_book, :earnings_quarterly_growth, :dividend,
                :dividend_yield, :payout_ratio, :last_dividend_date, :ex_dividend_date)
        ON CONFLICT(symbol, date) DO UPDATE SET
            market_cap = excluded.market_cap,
            enterprise_value = excluded.enterprise_value,
            trailing_pe = excluded.trailing_pe,
            forward_pe = excluded.forward_pe,
            peg_ratio = excluded.peg_ratio,
            price_to_book = excluded.price_to_book,
            earnings_quarterly_growth = excluded.earnings_quarterly_growth,
            dividend = excluded.dividend,
            dividend_yield = excluded.dividend_yield,
            payout_ratio = excluded.payout_ratio,
            last_dividend_date = excluded.last_dividend_date,
            ex_dividend_date = excluded.ex_dividend_date
    ''', company_info)

    # 変更を保存
    conn.commit()
    # 接続を閉じる
    conn.close()
    # 結果出力
    print(f"企業情報を更新しました: {symbol} - {today}")

if __name__ == "__main__":
    # 例としてAAPLとトヨタ自動車の企業情報を更新
    update_company_info("7203.T")

F5で実行してから、stocks.dbを確認。
2行目はスペルミスしてアップルを「APPL」としたので、データが取得できず、
nullが入ってます。恥ずかしい。。。😂

ここまで来て。。。

ミスというほどではないのですが、企業情報テーブルはsymbolをキーに常に上書きしに行くので
symbolをキーにすればいいじゃんって気づきました。
ID削除してsymbolをプライマリキーに修正することに。

    # 企業情報テーブルの作成
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS company_info (
            symbol TEXT PRIMARY KEY,
            date TEXT NOT NULL,
            market_cap REAL,
            enterprise_value REAL,
            trailing_pe REAL,
            forward_pe REAL,
            peg_ratio REAL,
            price_to_book REAL,
            eps_growth_rate REAL,
            earnings_quarterly_growth REAL,
            dividend REAL,
            dividend_yield REAL,
            payout_ratio REAL,
            last_dividend_date TEXT,
            ex_dividend_date TEXT
        )
    ''')

DBを作り直しになるので、DB削除します。

rm stocks.db

で、もう1回init_dbをF5で実行。
idが消えて、symbolがキーになりました!

株価テーブル編

新規ファイル「update_stock_prices.py」を作成し、コードを記述していきます。
企業情報テーブルとほぼ同じようなコーディングです。
共通のレコード追加変更関数作ればいいのかなって思いましたが、
テーブルが増えそうだなと感じたときにやります🤮
そんなわけでコード。

import sqlite3
import yfinance as yf
from datetime import datetime, timedelta

def update_strock_prices(symbol: str):
    # yfinanceを使用して指定銘柄の株価データを取得
    ticker = yf.Ticker(symbol)
    hist = ticker.history(period="1d")  # 最新の株価データを取得

    # データが取得できなかった場合の処理
    if hist.empty:
        print(f"株価データがありませんでした: {symbol}")
        return

    # 最新の株価データを取得
    latest_data = hist.iloc[-1]
    date = latest_data.name.strftime("%Y-%m-%d")

    # データベースに接続
    conn = sqlite3.connect('stocks.db')
    cursor = conn.cursor()

    # 株価データの準備
    stock_price = {
        "symbol": symbol,
        "date": date,
        "open": latest_data['Open'],
        "high": latest_data['High'],
        "low": latest_data['Low'],
        "close": latest_data['Close'],
        "volume": int(latest_data['Volume'])
    }

    
    # 株価データをデータベースに挿入または更新
    columns = [
        "symbol", "date", "open", "high", "low", "close", "volume"
    ]
    insert_columns = ', '.join(columns)
    placeholders = ', '.join([':' + col for col in columns])
    update_columns = ', '.join([f"{col} = excluded.{col}" for col in columns[2:]])  # open, high, low, close, volume
    sql = f'''
        INSERT INTO stock_prices ({insert_columns})
        VALUES ({placeholders})
        ON CONFLICT(symbol, date) DO UPDATE SET
            {update_columns}
    '''
    # SQL文を実行
    cursor.execute(sql, stock_price)

    # 変更を保存して接続を閉じる
    conn.commit()
    conn.close()
    print(f"株価データを更新しました: {symbol} - {date}")

if __name__ == "__main__":
    # 例としてAAPLの株価データを更新
    update_strock_prices("AAPL")    # Apple
    update_strock_prices("7203.T")  # トヨタ自動車

実行すると、直近のアップルとトヨタ自動車の株価が株価テーブルに追加されます。
DBに個別銘柄の情報を登録することができたので、次回は東証プライムの企業情報と
株価を一括登録にチャレンジします。

Discussion