株のシステムトレードをしよう - 1から始める株自動取引システムの作り方

株式をコンピュータに売買させる仕組みを少しずつ作っていきます。できあがってから公開ではなく、書いたら途中でも記事として即掲載して、後から固定ページにして体裁を整える方式で進めていきます。

アイデアメモ - CSVに調整後終値を付与(一旦実装完了と凡ミス)

結論

CSVを DataFrame に変換し、調整後終値を付加することはできた。しかし、凡ミスが1点だけあり、修正が必要。

Python のソースコード

import sys
import re
import dill
import jpbizday
import pandas as pd
import numpy as np
from bs4 import BeautifulSoup
from typing import List
from datetime import datetime, date
from dateutil.parser import parse

def get_adj_rate(paths_to_html: List[str]) -> pd.DataFrame:
    '''
    与えたHTMLファイルから、終値調整比を作成して、DFで返す。

    Parameters
    ---------------------
    path_to_html: List[str]
        相対パスor絶対パス
    
    Returns
    ---------------------
    dataframe: pd.DataFrame
    '''
    def _convert_to_ratio(nl: str) -> float: 
        before, after = re.split('[→:]', nl.replace('株', ''))
        return float(after) / float(before)

    def _html2df(path_to_html: str) -> pd.DataFrame:
        html = open(path_to_html).read()

        soup = BeautifulSoup(html, 'html.parser')
        table = soup.find('table', {'class': 'tbl01'})
        rows = table.findAll('tr')
        csv = [
            [cell.get_text() for cell in row.findAll(['td', 'th'])]
            for row in rows
        ]
        df = pd.DataFrame(csv, columns=csv.pop(0)) \
                .rename(columns={'銘柄コード': 'code',  # 扱いやすいように半角にしておく
                                '銘柄名': 'name',
                                '併合比率': 'rate', '割当比率': 'rate',
                                '権利付最終日': 'from'})
        return df

    def _adj_rates(df_in_desc: pd.DataFrame) -> list:
        '''
        Parameters
        ----------------------
        df_in_desc: pd.DataFrame
            日時降順でソートし与えること。
        '''
        adj_rates_in_each_codes: dict = {}
        if sys.version_info.major == 3 and sys.version_info.minor >= 8:
            pass
            # 動作未検証:
            # return [adj_rates_in_each_codes[code] := adj_rates_in_each_codes.get(code, 1.0) / rate
            #         for code, rate in df_in_desc[['code', 'rate']].values]
        else:
            def calc_adj_rates(code: str, rate: float):
                adj_rates_in_each_codes[code] = adj_rates_in_each_codes.get(code, 1.0) / rate
                return adj_rates_in_each_codes[code]
            return [calc_adj_rates(code, rate)
                for code, rate in df_in_desc[['code', 'rate']].values]

    def _reverse(df: pd.DataFrame) -> pd.DataFrame:
        # https://stackoverflow.com/a/20444256
        return df.iloc[::-1]

    dfs = pd.concat(_html2df(path_to_html) for path_to_html in paths_to_html) \
            .sort_values(['code', 'from'], ascending=False)
    dfs['rate'] = dfs['rate'].apply(_convert_to_ratio)
    dfs['adj_rate'] = _adj_rates(dfs)
    dfs['adj_rate'] = dfs['adj_rate'].astype(np.float64)
    dfs['date'] = dfs['from'].apply(three_separated_digits_to_date) #lambda x: date(*map(lambda y: int(y), x.split('/'))))
    return _reverse(dfs)[['code', 'name', 'date', 'rate', 'adj_rate']]

def save_as_dill(df: pd.DataFrame, path_to_dill: str='adj_rates.dill'):
    dill.dump(adj_rate_df, open(path_to_dill, 'wb'))

def load_from_dill(path_to_dill: str='adj_rates.dill') -> pd.DataFrame:
    return dill.load(open(path_to_dill, 'rb'))
    
def three_separated_digits_to_date(date_str: str) -> datetime.date:
    '''
    YYYY-MM-DD や YYYY/MM/DD や 'YYYY MM DD' のstrをパースして、dateで返す。
    '''
    return date(*map(lambda x: int(x), re.split('[-/ ]', date_str)[0:3]))


def hist_data(code: str, year: str) -> pd.DataFrame:
    '''
    銘柄コード、年を指定して、CSVを読み込み、DFを返す。
    '''
    filepath = f'/content/drive/MyDrive/Project/kabu-plus/japan-stock-prices-2_{year}_{code}.csv'
    csv = pd.read_csv(filepath, encoding='shift_jis')
    columns = {'SC': 'code', '名称': 'name', '市場': 'market', '業種': 'industry', \
                        '日時': 'date', '株価': 'close', '始値': 'open', '高値': 'high', '安値': 'low',
                        '出来高': 'volumes'}
    csv = csv.rename(columns=columns)
    csv['date'] = csv['date'].apply(three_separated_digits_to_date)
    return csv.loc[:, columns.values()]

def hist_data_with_adj_close(code: str, year: str,
                             adj_rate_df: pd.DataFrame) -> pd.DataFrame:
    '''
    銘柄コード、年、終値調整用比のDFから、調整後終値付きのDFを返す。
    '''
    bizdays = pd.DataFrame({'date': jpbizday.year_bizdays(year)}).set_index('date')
    adj_rate_for_current_stock: pd.DataFrame = adj_rate_df[adj_rate_df['code'] == code]
    ret = bizdays.merge(adj_rate_for_current_stock, \
                        on='date', how='left').fillna(method='ffill').fillna(1.0).set_index('date')
    adj = ret.loc[:, ['adj_rate']]
    hist = hist_data(code, year)
    hist = hist.merge(adj, on='date', how='left')
    latest_rate = hist.iloc[-1]['adj_rate']
    hist['adj_close'] = hist['close'] * (latest_rate / hist['adj_rate'])
    hist = hist.set_index('date')
    return hist


from IPython.display import display
pd.set_option('display.max_rows', 500)

adj_rate_df = get_adj_rate(['/content/drive/MyDrive/Project/kabu-plus/heigou.html', \
                            '/content/drive/MyDrive/Project/kabu-plus/bunkatsu.html'])
year = 2020
codes = ['9143']
for code in codes:
    display(hist_data_with_adj_close(code, year, adj_rate_df))

どこがうまくいっていないのか

時間がないので詳細を省くが、明らかに10月28日の調整後終値蛾おかしい。

明らかに28日だけおかしい
明らかに28日だけおかしい

この銘柄の場合、10月28日の場が閉まった後、株式1株が2株に分割されるので、正しくは10月28日の終値ではなく、29日の終値から分割後の比率を適用した方が良さそうだ。

(C) 2020 dogwood008 禁無断転載 不許複製 Reprinting, reproducing are prohibited.