pagetakaBlog

最近リフォームと鳥取県日南町の記事多め。写真、PC、ネット等の話題も

Python:SQLAlchemy使いMySQLからPandas.DataFameへデータ取得[スクリプト付]。

SQLAlchemyでMySQLデータ取得することができました。Microsoft.Copilotが教えてくれました。備忘録として、スクリプトと一緒に記事にしました。

pandas.DataFrameがSQLAlchemyを使うように警告表示。

MySQLからデータ取得するのに今まで通りの方法でやってましたら、「pandas..DataFrameはSQLAlchemy経由でしか面倒見てないから」的なことがvscodeのターミナルに表示されました。データ取得はできてたんですが、その都度警告表示が出るのも「なんだかな~」的な雰囲気が。

普段と異なるMySQL接続・SQL文というのは、チョット厄介に感じた爺でした。

Copilotに尋ねたら、教えてくれましたので、この記事を備忘録として残すことにしました。

まず、importする道具が三つあります。
そもそも、dl→installまだだったらやっとく必要があります。vscodeのターミナル上で一つずつやってください。

pip install pandas
pip install sqlalchemy
pip install dataclasses

おことわり:MySQL上にデータベース名stock2、テーブル名market2を用意し、データ置いてないとエラーがでます。

おもにSQLAlchemyのことを書くので、MySQLのSQL文がわかり、Pythonからの操作ができるコトを前提にし、手抜きさせてもらいます。

MySQLtono接続にはエンジンが必要みたい。

スクリプトの前半です。

# MySQL(stock2.market2)から単cdごとにdate, Open, High, Low, Close取得しpandas.DataFrameへ収める
# market2構造:num=int(自動増番)+primary, date=date, cd=varchar(4), Open...Close=double(注目株価に依存)
# このスクリプトは、MySQLを用意し、必要なデータを置いてないと動きません。悪しからず。
import pandas as pd
import sqlalchemy as sa
import dataclasses

@dataclasses.dataclass
class DB:
    user : str = 'root'
    pw: str = ''
    host : str = 'localhost'
    database : str = 'stock2'
    table : str = 'market2'

engine = sa.create_engine(f'mysql+pymysql://{DB.user}:{DB.pw}@{DB.host}/{DB.database}?charset=utf8')

最後の行、爺的にはずいぶんな違和感でした。このような仕様、と思うほかありませぬ。「f文使い、mysql+pymysql ってどういう仕掛け?」 とも思いました。
データクラス使っているので、慣れてないと読みにくいと思われるかもしれませんが、悪しからず。

プレースホルダーの書き方が異なる...%(xx)s。

ひとつ上のスクリプトとつなげると、今回のスクリプト全体です。

def get_start_date():
    # market2内の一番古い日付を取得
    query = f"SELECT MIN(date) AS date FROM {DB.table}"
    df = pd.read_sql(query, con=engine)
    return df.iloc[0]["date"]  # DataFrameの値(一番古い日付)を返す

def try_data_provider(a_cd):    #target MySQL = stock2.market2
    # 単cdをプレースホルダーに代入し、データをmarket2から取得
    query = f"SELECT date, Open, High, Low, Close FROM {DB.table} WHERE cd = %(cd)s"
    condition = {"cd" :a_cd}
    df = pd.read_sql(query,  con=engine, params=condition)    
    return df

def roop_each_cds(cds, start_date):
    # グラフ描画スクリプトですが省略    
    for i,a_cd in enumerate(cds):
        df = try_data_provider(a_cd)
        print(f'{i}:{a_cd}\t{df[:2]}')
        # 0:1720           date   Open   High    Low  Close
        # 0  2025-03-13  780.0  785.0  776.0  785.0
        # 1  2025-03-14  780.0  787.0  778.0  785.0
        # 1:198A           date   Open   High    Low  Close
        # 0  2025-03-13  612.0  620.0  605.0  617.0
        # 1  2025-03-14  617.0  617.0  608.0  611.0

def main():
    start_date = get_start_date()
    cds = ['1720','198A'] # 仮テスト用
    roop_each_cds(cds, start_date)

if __name__ == "__main__":

    main()

このスクリプトの中では、次の部分が独特と感じました。

    query = f"SELECT date, Open, High, Low, Close FROM {DB.table} WHERE cd = %(cd)s"
    condition = {"cd" :a_cd}
    df = pd.read_sql(query,  con=engine, params=condition) 

プレースホルダの書き方が「%(cd)s」と名前があって、そこにいれる値は辞書型「 {"cd" :a_cd}」で示し、それを「params=condition」を加えたread_sqlで処理している。結果は、pandas>DataFrameとしてdfに代入された、という流れでした。

関数使ったり、「if __name__ == "__main__":」があったり、面倒に感じるのは爺だけではないのかとも想像。

セキュリティの都合で「if __name__ == "__main__":」をつかったり、処理毎に関数を分け、メンテしやすくするなどしています。ほぼコレは受け売りで、爺はよく理解してません。
「cds = ['1720','198A'] # 仮テスト用」は仮に用意した銘柄コードです。株価データを全部処理しようとすると莫大な数になりますので、目星をつけたいくつかの銘柄で試すのが簡便ではなかろうかと爺は思います。

df取得後、株価関連のグラフ描画を行う...という部分をスクリプトから落としています。長くなるので割愛しました。悪しからず。現在、四本足、VWAP折れ線グラフ、ボリンジャーバンド±2αを表示し、同一窓に出来高をY右軸に株を表示し、透過処理した棒グラフを重ねて表示、というところまでできました。またの機会にでも....。