2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

アプリ計画の大雑把な構成とPythonでFastAPIを使って
仮のデータを出力するところまでやったので、
本格的にDBにデータを保管するところとか作っていきたい。

前回の記事

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に個別銘柄の情報を登録することができたので、次回は東証プライムの企業情報と
株価を一括登録にチャレンジします。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?