ポートフォリオ可視化スクリプト(4) - 投資適格判断のスクリーニング -¶
コード¶
stock_toolkit.py¶
import os, sys, shutil, json5, datetime, tqdm, re, time, requests
import concurrent.futures
import jinja2
import itertools as it
import yfinance as yf
import numpy as np
import pandas as pd
import nk_toolkit.math.round__digit as rdg
sensitivesList = [ "鉱業", "石油・石炭製品", "ガラス・土石製品", "鉄鋼", "非鉄金属", "金属製品",
"化学", "ゴム製品", "パルプ・紙", "繊維製品", "機械", "輸送用機器", "電気機器",
"精密機器", "その他製品", "陸運業", "海運業", "空運業", "倉庫・運輸関連業",
"建設業", "不動産業", "卸売業" ]
defensivesList = [ "食料品", "医薬品", "電気・ガス業", "小売業", "銀行業", "保険業", \
"証券、商品先物取引業", "その他金融業", "サービス業", "情報・通信業" ]
othersList = [ "-" ]
# ========================================================= #
# === analyze__stockStatus.py === #
# ========================================================= #
def analyze__stockStatus( settingFile="dat/settings.json" ):
# ------------------------------------------------- #
# --- [1] load settings.json --- #
# ------------------------------------------------- #
with open( settingFile, "r" ) as f:
params = ( json5.load( f ) )
settings = params["settings"]
nyears = settings["nyears"]
# ------------------------------------------------- #
# --- [2] load previous database --- #
# ------------------------------------------------- #
try:
with open( settings["databaseFile"], "r" ) as f:
data_ = json5.load( f )
except:
print( "[analyze__stockStatus.py] no databaseFile :: {} "\
.format( settings["databaseFile"] ) )
data_ = {}
# ------------------------------------------------- #
# --- [3] prepare ticker from list --- #
# ------------------------------------------------- #
if ( settings["mode"] in ["all"] ):
jpx_list = pd.read_csv( settings["filteredFile"] )
candidate = settings["markets"]
params["tickers"] = jpx_list[ jpx_list["市場・商品区分"].isin( candidate ) ]["コード"]
elif ( settings["mode"] in [ "select" ] ):
pass
else:
print( "[stock_toolkit.py] unrecognizable mode ... {} ".format( params["mode"] ) )
sys.exit()
if ( settings["tickersFile"] is not None ):
stocklist = pd.read_csv( settings["tickersFile"] )
name_map = dict(zip(stocklist["コード"], stocklist["銘柄名"]))
sector_map = dict(zip(stocklist["コード"], stocklist["33業種区分"]))
stocklist = { "name":name_map, "sector":sector_map }
# ------------------------------------------------- #
# --- [4] skip already stored in database --- #
# ------------------------------------------------- #
skips = []
fmt = "%Y/%m/%d"
today = datetime.datetime.today().strftime('%Y/%m/%d')
if ( settings["skip"] ):
for ik,tic in enumerate( params["tickers"] ):
if ( tic in data_ ):
date_db = datetime.datetime.strptime( data_[tic]["date"], fmt ).date()
date_nw = datetime.datetime.strptime( today, fmt ).date()
if ( date_db >= date_nw ):
print( "[stock_toolkit.py] ticker = {}'s data is up-to-date... [skip]"\
.format( tic ) )
skips += [ tic ]
params["tickers"] = [ tic for tic in params["tickers"] if tic not in skips ]
# ------------------------------------------------- #
# --- [5] seek info of a stock --- #
# ------------------------------------------------- #
stack = []
def workers( ticker, nyears, stocklist, params ):
ret = seek__stockinfo( ticker=ticker, \
nyears=nyears, stocklist=stocklist, params=params )
return( ret )
with concurrent.futures.ThreadPoolExecutor( max_workers=settings["nParallels"] ) as ex:
futures = [ ex.submit( workers, tic, nyears, stocklist, params ) for tic in params["tickers"] ]
for fu in tqdm.tqdm( concurrent.futures.as_completed( futures ), total=len(futures) ):
stack += [ fu.result() ]
rets = { stock["ticker"]:stock for stock in stack }
data = { **data_, **rets }
# ------------------------------------------------- #
# --- [6] own flags / sector info --- #
# ------------------------------------------------- #
for tic in params["owns"]:
if ( tic in data ):
data[tic]["own"] = True
else:
print( "[stock_toolkit.py] no database data of {}.. cannot add own flag.. [WARNING]"\
.format( tic ) )
with open( settings["databaseFile"], "w", encoding="utf-8" ) as f:
json5.dump( data, f, indent=4, quote_keys=True )
return( data )
# tickers = [ "{}.T".format( tic ) for tic in params["tickers"] ]
# tickers = tickers[:100]
# tickers = yf.Tickers( tickers )
# stack = {}
# import tqdm.contrib.concurrent
# import functools
# func = functools.partial( seek__stockinfo, nyears=nyears, rename=rename, params=params )
# rets = tqdm.contrib.concurrent.thread_map( func, params["tickers"], \
# max_workers=settings["nParallels"] )
# stack = { key:rets[ik] for ik,key in enumerate(params["tickers"]) }
# with concurrent.futures.ThreadPoolExecutor( max_workers=settings["nParallels"] ) as ex:
# rets = ex.map( workers, params["tickers"], \
# it.repeat( nyears ), it.repeat( rename ), it.repeat( params ) )
# rets = list( rets )
# stack = { key:rets[ik] for ik,key in enumerate(params["tickers"]) }
# futures = [ ex.submit( workers, tickers, tic, nyears, rename, params ) \
# for tic in params["tickers"] ]
# for fu in tqdm.tqdm( concurrent.futures.as_completed( futures ), total=len(futures) ):
# stack[tic] = fu.()
# for ik,tic in enumerate( tqdm.tqdm( params["tickers"] ) ):
# stack[tic] = seek__stockinfo( tickers=tickers, ticker=tic, \
# nyears=nyears, rename=rename, params=params )
# if ( ik%20 == 0 ): # -- 途中セーブ -- #
# data = { **data_, **stack }
# with open( settings["databaseFile"], "w", encoding="utf-8" ) as f:
# json5.dump( data, f, indent=4, quote_keys=True )
# ========================================================= #
# === make HTML file for display === #
# ========================================================= #
def makehtml__stockStatus( settingFile="dat/settings.json" ):
# ------------------------------------------------- #
# --- [1] load settings.json / database.json --- #
# ------------------------------------------------- #
with open( settingFile, "r" ) as f:
params = ( json5.load( f ) )
settings = params["settings"]
# ------------------------------------------------- #
# --- [2] set keys --- #
# ------------------------------------------------- #
keys = {}
for key in params["rangecheck"].keys():
rmin = ( "{0}".format( params["rangecheck"][key]["min"] ) ).replace( "None", " " )
rmax = ( "{0}".format( params["rangecheck"][key]["max"] ) ).replace( "None", " " )
keys[key] = params["rangecheck"][key]["name"] + " ( {0} - {1} )".format( rmin, rmax )
for key in params["increment"].keys():
keys[key] = params["increment"][key]["name"]
# ------------------------------------------------- #
# --- [3] load previous database --- #
# ------------------------------------------------- #
try:
with open( settings["databaseFile"], "r" ) as f:
data_ = json5.load( f )
except:
print( "[analyze__stockStatus.py] no databaseFile :: {} "\
.format( settings["databaseFile"] ) )
data_ = {}
# ------------------------------------------------- #
# --- [4] sort by score --- #
# ------------------------------------------------- #
if ( settings["sort"] ):
tickers = pd.Series( [ key for key in data_.keys() ] )
scores = pd.Series( [ data_[key]["score"] for key in data_.keys() ] )
df = pd.DataFrame( { "ticker":tickers, "score":scores } )
df = df.sort_values( "score", ascending=False )
sortkeys = list( df["ticker"] )
data_ = { key:data_[key] for key in sortkeys }
data = [ data_[key] for key in data_.keys() ]
# ------------------------------------------------- #
# --- [5] render html file with jinja2 --- #
# ------------------------------------------------- #
env = jinja2.Environment( loader=jinja2.FileSystemLoader("templates") )
template = env.get_template( settings["template.detail"] )
html_output = template.render( data=data, keys=keys )
shutil.copy( settings["cssFile"], "html/" )
with open( settings["html.fancy"], "w", encoding="utf-8" ) as f:
f.write( html_output )
print(" output :: {}".format( settings["html.fancy"] ) )
# ------------------------------------------------- #
# --- [6] render screening table using jinja2 --- #
# ------------------------------------------------- #
today = datetime.datetime.today().strftime("%Y-%m-%d")
env = jinja2.Environment( loader=jinja2.FileSystemLoader("templates") )
template = env.get_template( settings["template.table"] )
html_output = template.render( data=data_, keys=keys, today=today )
shutil.copy( settings["cssFile"], "html/" )
with open( settings["html.screening"], "w", encoding="utf-8" ) as f:
f.write( html_output )
print(" output :: {}".format( settings["html.screening"] ) )
return()
# ========================================================= #
# === seek__stockinfo.py === #
# ========================================================= #
def seek__stockinfo( tickers=None, ticker=None, stocklist=None, digit=3, nyears=15, params=None ):
# ------------------------------------------------- #
# --- [1] 財務データ取得 --- #
# ------------------------------------------------- #
if ( tickers is None ):
hticker = yf.Ticker( ticker+".T" )
else:
hticker = tickers.tickers[ ticker+".T" ]
financials = hticker.financials
balance_sheet = hticker.balance_sheet
cashflow = hticker.cashflow
fast_info = hticker.fast_info
bs_items = balance_sheet.index
pl_items = financials.index
cf_items = cashflow.index
results = {}
# ------------------------------------------------- #
# --- [2] 基本情報 --- #
# ------------------------------------------------- #
results["ticker"] = ticker
results["name"] = ticker
results["date"] = datetime.datetime.today().strftime('%Y/%m/%d')
results["own"] = False
price = hticker.fast_info.last_price
shares = hticker.get_shares_full()
net_income = np.nan
equity = np.nan
total_assets = np.nan
eps = np.nan
bps = np.nan
if shares is not None:
shares = shares.sort_index().iloc[-1]
else:
shares = balance_sheet.loc["Ordinary Shares Number"]
if ( shares is not None ) and ( len(shares) > 0 ):
shares = shares.sort_index().iloc[0]
if ( "Net Income" in pl_items ):
net_income = financials.loc["Net Income"]
if ( "Common Stock Equity" in bs_items ):
equity = balance_sheet.loc["Common Stock Equity"].iloc[0]
if ( "Total Assets" in bs_items ):
total_assets = balance_sheet.loc["Total Assets"].iloc[0]
results["currentPrice"] = price
if ( "Net Income Common Stockholders" in hticker.ttm_income_stmt.index ):
ni_ttm = float( hticker.ttm_income_stmt.loc["Net Income Common Stockholders"].iloc[0] )
elif ( "Net Income" in hticker.ttm_income_stmt.index ):
ni_ttm = hticker.ttm_income_stmt.loc["Net Income"].iloc[0]
elif ( "Net Income Common Stockholders" in hticker.income_stmt.index ):
ni_ttm = float( hticker.income_stmt.loc["Net Income Common Stockholders"].iloc[0] )
elif ( "Net Income" in hticker.income_stmt.index ):
ni_ttm = hticker.income_stmt.loc["Net Income"].iloc[0]
elif ( np.isfinite( net_income ).any() ):
ni_ttm = net_income.iloc[0]
else:
ni_ttm = np.nan
if ( np.isfinite( ni_ttm ) and np.isfinite( shares ) ):
eps = ni_ttm / shares
if ( np.isfinite( equity ) and np.isfinite( shares ) ):
bps = equity / shares
# ------------------------------------------------- #
# --- [3] 売上高推移 --- #
# ------------------------------------------------- #
# -- [3-1] 売上高推移 -- #
try:
results["revenues"] = list( ( financials.loc["Total Revenue"] ).to_numpy() )
except KeyError:
results["revenues"] = np.nan
# -- [3-2] 営業利益率 -- #
try:
operating_income = financials.loc["Operating Income"]
results["oprIncome"] = operating_income.iloc[0] / results["revenues"][0] * 100.0 # [%]
except Exception:
results["oprIncome"] = np.nan
# ------------------------------------------------- #
# --- [4] 売上高・経常利益・純利益の推移 --- #
# ------------------------------------------------- #
results["revenues.inc"] = pd.Series([], dtype="float64")
results["dividend.inc"] = pd.Series([], dtype="float64")
results["oprIncome.inc"] = pd.Series([], dtype="float64")
results["netIncome.inc"] = pd.Series([], dtype="float64")
results["EPS.inc"] = pd.Series([], dtype="float64")
dividend = hticker.dividends
dividend = correct__latest_dividend( dividend )
if ( not( dividend.empty ) ):
results["dividend.inc"] = pd.Series( dividend["dividend"].values )
if ( "Total Revenue" in pl_items ):
results["revenues.inc"] = financials.loc["Total Revenue"] / 1.0e8
if ( "Operating Income" in pl_items ):
results["oprIncome.inc"] = operating_income / 1.0e8
if ( "Net Income" in pl_items ):
results["netIncome.inc"] = net_income / 1.0e8
if ( "Net Income" in pl_items ) and ( "Ordinary Shares Number" in bs_items ):
results["EPS.inc"] = net_income / balance_sheet.loc["Ordinary Shares Number"]
elif ( "Share Issued" in bs_items ) and ( np.isfinite(results["netIncome.inc"] ).any() ):
results["EPS.inc"] = results["netIncome.inc"] / balance_sheet.loc["Share Issued"]
for key in ["oprIncome.inc", "revenues.inc", "netIncome.inc", "EPS.inc", "dividend.inc" ]:
values = ( ( (results[key].sort_index()).dropna() ).values ).astype( int )
results[key] = list( [ int(val) for val in values[::-1][:nyears][::-1] ] )
# try:
# values = ( ( results[key].dropna() ).values ).astype( int )
# results[key] = list( [ int(val) for val in values[::-1] ] )
# except:
# results[key] = np.nan
if ( np.isnan( eps ) and len( results["EPS.inc"] ) > 0 ):
eps = results["EPS.inc"][-1]
# ------------------------------------------------- #
# --- [5] 配当利回り, 配当性向 --- #
# ------------------------------------------------- #
results["dividend.yield"] = np.nan
results["dividend.ratio"] = np.nan
if ( not( dividend.empty ) and ( np.isfinite(price) ) ):
results["dividend.yield"] = dividend["dividend"].iloc[-1] / price * 100.0
if ( not( dividend.empty ) and ( len( results["EPS.inc"] ) > 0 ) ):
results["dividend.ratio"] = dividend["dividend"].iloc[-1] / eps * 100.0
# ------------------------------------------------- #
# --- [6] EPS / PER / PBR --- #
# ------------------------------------------------- #
results["EPS"] = np.nan
results["PER"] = np.nan
results["PBR"] = np.nan
if np.isfinite( eps ):
results["EPS"] = eps
if np.isfinite( eps ) and np.isfinite( price ):
results["PER"] = price / eps
if np.isfinite( bps ) and np.isfinite( price ):
results["PBR"] = price / bps
# ------------------------------------------------- #
# --- [7] ROE / ROA --- #
# ------------------------------------------------- #
results["ROE"] = np.nan
results["ROA"] = np.nan
if ( np.isfinite( ni_ttm ) and np.isfinite( equity ) ):
results["ROE"] = ni_ttm / equity * 100.0 # [%]
if ( np.isfinite( ni_ttm ) and np.isfinite( total_assets ) ):
results["ROA"] = ni_ttm / total_assets * 100.0 # [%]
# ------------------------------------------------- #
# --- [8] 自己資本比率 / 流動比率 / 現金比率 --- #
# ------------------------------------------------- #
# -- [8-1] 自己資本比率 -- #
try:
results["equityRatio"] = equity / total_assets * 100.0 # [%]
except:
results["equityRatio"] = np.nan
# -- [8-2] 流動比率 -- #
try:
current_assets = balance_sheet.loc["Current Assets"].iloc[0]
current_liabilities = balance_sheet.loc["Current Liabilities"].iloc[0]
results["currentRatio"] = current_assets / current_liabilities * 100.0 # [%]
except Exception:
results["currentRatio"] = np.nan
# -- [8-3] 現金比率 -- #
try:
cash = balance_sheet.loc["Cash And Cash Equivalents"].iloc[0]
results["cashRatio"] = cash / total_assets * 100.0 # [%]
except Exception:
results["cashRatio"] = np.nan
# ------------------------------------------------- #
# --- [9] 有利子負債比率 --- #
# ------------------------------------------------- #
# -- income / depreciation :: denominator -- #
if ( ( "Operating Income" in pl_items ) and ( "Depreciation" in cf_items ) ):
oprat_income = financials.loc["Operating Income"].iloc[0]
depreciation = cashflow.loc["Depreciation"].iloc[0]
ebitda = oprat_income + depreciation
else:
ebitda = None
# -- debt -- #
if ( "Total Debt" in bs_items ):
debt = balance_sheet.loc["Total Debt"].iloc[0]
elif ( "Short Long Term Debt" in bs_items ):
debt = balance_sheet.loc["Short Long Term Debt"].iloc[0]
elif ( "Short Term Debt" in bs_items ) and ( "Long Term Debt" in bs_items ):
debt = balance_sheet.loc["Short Term Debt"].iloc[0] + \
balance_sheet.loc["Long Term Debt"].iloc[0]
else:
debt = None
# -- cash -- #
if ( "Cash And Cash Equivalents" in bs_items ):
cash = balance_sheet.loc["Cash And Cash Equivalents"].iloc[0]
else:
cash = None
# -- debtRatio -- #
if ( ebitda and debt and cash ):
results["debtRatio.ebitda"] = ( debt - cash ) / ebitda
else:
results["debtRatio.ebitda"] = np.nan
if ( debt and equity ):
results["debtRatio.equity"] = debt / equity * 100.0
else:
results["debtRatio.equity"] = np.nan
# ------------------------------------------------- #
# --- [10] return --- #
# ------------------------------------------------- #
results["check"] = check__stockinfo( stock=results, params=params )
results["score"] = int( np.sum( [ int(val) for val in results["check"].values() ] ) )
results["sensitive"] = False
max_score = len( results["check"].values() )
if ( stocklist is not None ):
results["name"] = stocklist["name"] .get( results["ticker"], results["name"] )
results["sector"] = stocklist["sector"].get( results["ticker"], "unknown" )
max_score = max_score + 1
if ( results["sector"] in sensitivesList ):
results["sensitive"] = True
else:
results["sensitive"] = False
results["score"] += 1
results["score_display"] = "{0}/{1}".format( results["score"], max_score )
results["score_bgcolor"] = score_to_color( results["score"], max_score )
if ( digit ):
import numbers
for key in results.keys():
if ( ( isinstance(results[key], numbers.Number) ) and not( np.isnan(results[key] ) ) ):
results[key] = rdg.round__digit( results[key], digit=digit )
for key in results.keys():
if ( isinstance( results[key], ( float, np.floating ) ) ):
if ( pd.isna( results[key] ) ): results[key] = "N/A"
return( results )
# TTM = info.get( "trailingAnnualDividendRate", np.nan )
# yfina_div = info.get( "dividendRate", np.nan )
# if ( results["dividend.inc"] != np.nan ): # -- 直近配当が中間配当までの場合を補正 -- #
# latest = results["dividend.inc"][-1]
# else:
# latest = np.nan
# if ( latest == TTM ) or ( latest == yfina_div ):
# results["dividend.yield"] = latest / price * 100.0
# elif ( TTM == yfina_div ):
# results["dividend.yield"] = TTM / price * 100.0
# else:
# vals = np.array( [ latest, TTM, yfina_div ] )
# div = np.median( vals[ ~np.isnan(vals) ] )
# results["dividend.yield"] = div / price * 100.0
# if ( TTM is not None ):
# if ( results["dividend.inc"] != np.nan ): # -- 直近配当が中間配当までの場合を補正 -- #
# latest_dividend = results["dividend.inc"][-1]
# if ( latest_dividend != TTM ):
# try:
# if ( results["dividend.inc"][-1] <= 0.5*results["dividend.inc"][-2] ):
# results["dividend.inc"][-1] = int(TTM)
# results["dividend.yield"] = int(TTM) / results["currentPrice"] * 100.0
# except:
# try:
# if ( results["dividend.inc"][-1] <= 0.5*results["dividend.inc"][-2] ):
# results["dividend.inc"].pop( -1 )
# try:
# results["dividend.yield"] = results["dividend.inc"][-1] / results["currentPrice"] * 100.0
# except:
# try:
# results["dividend.yield"] = info.get( "dividendRate", np.nan ) / results["currentPrice"] * 100.0
# except:
# results["dividend.yield"] = None
# except:
# pass
# ------------------------------------------------- #
# --- [8] 株価推移と移動平均線 --- #
# ------------------------------------------------- #
# try:
# history = hticker.history(period="5y")
# results["stock_price"] = history["Close"]
# results["ma50"] = history["Close"].rolling(window=50).mean()
# except:
# results["stock_price"] = np.nan
# results["ma50"] = np.nan
# ========================================================= #
# === check__stockinfo.py === #
# ========================================================= #
def check__stockinfo( stock=None, settingFile="dat/settings.json", params=None ):
# ------------------------------------------------- #
# --- [1] load json settings --- #
# ------------------------------------------------- #
if ( params is None ):
with open( settingFile, "r" ) as f:
params = json5.load( f )
rangecheck = params["rangecheck"]
increment = params["increment"]
# ------------------------------------------------- #
# --- [2] range check --- #
# ------------------------------------------------- #
ret = {}
for key in rangecheck.keys():
ret[key] = True
if ( rangecheck[key]["min"] is not None ):
try:
if ( stock[key] <= rangecheck[key]["min"] ): ret[key] = False
except:
ret[key] = False
if ( rangecheck[key]["max"] is not None ):
try:
if ( stock[key] >= rangecheck[key]["max"] ): ret[key] = False
except:
ret[key] = False
# ------------------------------------------------- #
# --- [3] increment check --- #
# ------------------------------------------------- #
for key in increment.keys():
try:
values = np.array( [ float( val ) for val in stock[key] ] )
ret[key] = bool( np.all( np.diff( values ) >= 0.0 ) )
except:
ret[key] = False
# ------------------------------------------------- #
# --- [4] sector check --- #
# ------------------------------------------------- #
return( ret )
# ========================================================= #
# === score to color function === #
# ========================================================= #
def score_to_color( score, max_score=18 ):
ratio = max(0, min(1, score / max_score))
if ratio <= 0.5: # 青→白
t = ratio / 0.5
r = int(255 * t)
g = int(255 * t)
b = 255
else: # 白→オレンジ
t = (ratio - 0.5) / 0.5
r = 255
g = int(255 * (1 - 0.5 * t))
b = int(255 * (1 - t))
return f"rgb({r},{g},{b})"
# ========================================================= #
# === filter__jpx_stock_list === #
# ========================================================= #
def filter__jpx_stock_list( settingFile="dat/settings.json" ):
# ------------------------------------------------- #
# --- [1] load jpx list --- #
# ------------------------------------------------- #
with open( settingFile, "r" ) as f:
params = ( json5.load( f ) )
settings = params["settings"]
jpx_list = pd.read_csv( settings["tickersFile"] )
min_yield = settings["div.yield.min"]
candidate = settings["markets"]
tickerList = jpx_list[ jpx_list["市場・商品区分"].isin( candidate ) ]["コード"].to_list()
tickerList_ = " ".join( [ tic + ".T" for tic in tickerList ] )
yftickers = yf.Tickers( tickerList_ )
# ------------------------------------------------- #
# --- [2] calculate dividend yield --- #
# ------------------------------------------------- #
def calc__dividend_yield( tic, yftickers, min_yield ):
stock = yftickers.tickers[tic+".T"]
price = stock.fast_info.last_price
divs = stock.dividends
dividend = correct__latest_dividend( divs )
if ( divs.empty or not( np.isfinite( price ) ) ):
return None
else:
div_yield = dividend["dividend"].iloc[-1] / price * 100.0
if ( div_yield > min_yield ):
return tic
else:
return None
# ------------------------------------------------- #
# --- [3] calculate dividend yieldrate --- #
# ------------------------------------------------- #
stack = []
with concurrent.futures.ThreadPoolExecutor( max_workers=settings["nParallels"] ) as ex:
futures = [ ex.submit( calc__dividend_yield, tic, yftickers, min_yield ) \
for tic in tickerList]
for fu in tqdm.tqdm( concurrent.futures.as_completed( futures ), total=len(futures) ):
ret = fu.result()
if ( ret is not None ):
stack += [ ret ]
filtered = jpx_list[ jpx_list["コード"].isin(stack) ]
# ------------------------------------------------- #
# --- [4] save in a file --- #
# ------------------------------------------------- #
filtered.to_csv( settings["filteredFile"] )
return( filtered )
# stack = []
# for tic in tqdm.tqdm(tickerList):
# price = np.nan
# div_yield = np.nan
# price = yftickers.tickers[ tic+".T" ].fast_info.last_price
# dividend_ = yftickers.tickers[ tic+".T" ].dividends
# dividend = correct__latest_dividend( dividend_ )
# if ( not( dividend.empty ) and ( np.isfinite( price ) ) ):
# div_yield = dividend["dividend"].iloc[-1] / price * 100.0
# if ( div_yield > settings["div.yield.min"] ):
# stack += [ tic ]
# filtered = jpx_list[ jpx_list["コード"].isin(stack) ]
# ========================================================= #
# === correct__dividends === #
# ========================================================= #
def correct__latest_dividend( divs: pd.Series, threshold=0.70, \
days=365, tz="Asia/Tokyo") -> pd.DataFrame:
# ------------------------------------------------- #
# --- [1] vacant / None input --- #
# ------------------------------------------------- #
if ( divs is None ) or ( divs.empty ):
return pd.DataFrame( columns=["date", "配当額"] )
# ------------------------------------------------- #
# --- [2] Date settings / calc annual dividend --- #
# ------------------------------------------------- #
s = pd.Series( divs ).dropna().sort_index()
s.index = pd.to_datetime( s.index )
s.index = s.index.tz_localize(tz) if s.index.tz is None else s.index.tz_convert(tz)
annual = s.resample("YE-DEC").sum().to_frame(name="dividend")
# ------------------------------------------------- #
# --- [3] check latest dividend --- #
# ------------------------------------------------- #
if len(annual) >= 2:
last_val = float( annual["dividend"].iloc[-1] )
prev_val = float( annual["dividend"].iloc[-2] )
if ( prev_val > 0 ) and ( last_val <= threshold*prev_val ):
ttm_cut = s.index.max() - pd.Timedelta( days=days )
ttm_sum = float( s[s.index > ttm_cut ].sum() )
annual.iloc[ -1, annual.columns.get_loc("dividend")] = ttm_sum
# ------------------------------------------------- #
# --- [4] return --- #
# ------------------------------------------------- #
ret = annual.reset_index().rename( columns={"index": "Date"} )
ret["Date"] = ret["Date"].apply(lambda x: x.replace(hour=0, minute=0, second=0, microsecond=0).isoformat(sep=" "))
return( ret[ ["Date", "dividend"] ] )
# ========================================================= #
# === Execution of Pragram === #
# ========================================================= #
if ( __name__=="__main__" ):
analyze__stockStatus()
makehtml__stockStatus()
settings.json¶
{
"rangecheck": {
"dividend.yield" : { "name": "配当利回り (%)" , "min":3.5, "max":5.5, },
"dividend.ratio" : { "name": "配当性向 (%)" , "min":10.0, "max":50.0, },
"oprIncome" : { "name": "営業利益率 (%)" , "min":10.0, "max":null, },
"PER" : { "name": "PER" , "min":5.0, "max":15.0, },
"PBR" : { "name": "PBR" , "min":0.5, "max":1.5, },
"ROE" : { "name": "ROE (%)" , "min":8.0, "max":null, },
"ROA" : { "name": "ROA (%)" , "min":5.0, "max":null, },
"equityRatio" : { "name": "自己資本比率 (%)" , "min":50.0, "max":null, },
"currentRatio" : { "name": "流動比率 (%)" , "min":200.0, "max":null, },
"cashRatio" : { "name": "現金比率 (%)" , "min":30.0, "max":null, },
"debtRatio.ebitda" : { "name": "EBITDA有利子負債倍率(倍)" , "min":null, "max":3.0, },
"debtRatio.equity" : { "name": "有利子負債倍率 (%)" , "min":null, "max":200, },
},
"increment": {
"revenues.inc" : { "name":"売上高 (億円)" },
"oprIncome.inc" : { "name":"営業利益 (億円)" },
"netIncome.inc" : { "name":"純利益 (億円)" },
"EPS.inc" : { "name":"EPS (円)" },
"dividend.inc" : { "name":"1株配当 (円)" },
},
// "tickers": [
// "8593",
// ],
"tickers": [
"2169", "2267", "2914", "3076", "3479", "3763", "3817", "4008",
"4752", "4928", "5020", "5108", "5192", "5334", "5388", "5401",
"5464", "5589", "6113", "6247", "6268", "6301", "7247", "7820",
"7921", "7994", "7995", "8002", "8053", "8058", "8316", "8411",
"8424", "8425", "8439", "8584", "8593", "8898", "9432", "9433",
"9513", "9795", "9799", "9986", "1605", "6501",
],
"owns": [
"2169", "2267", "2914", "3076", "3479", "3763", "3817", "4008",
"4752", "4928", "5020", "5108", "5192", "5334", "5388", "5401",
"5464", "5589", "6113", "6268", "6301", "7247", "7820",
"7921", "7994", "7995", "8002", "8053", "8058", "8316", "8411",
"8424", "8425", "8439", "8584", "8593", "8898", "9432", "9433",
"9513", "9795", "9799", "9986", "1605", "6501",
],
"settings":{
"mode" : "all" , // "all", "select"
"skip" : false , // whether skip or not.
"div.yield.min" : 3.0 , // minimum dividend rate
"cssFile" : "templates/custom.css" ,
"databaseFile" : "dat/database.json" ,
"tickersFile" : "dat/jpx_tickers.csv" ,
"filteredFile" : "dat/filtered_tickers.csv" ,
"markets" : [ "プライム(内国株式)", "スタンダード(内国株式)" ] ,
// [ "プライム(内国株式)", "スタンダード(内国株式)", "グロース(内国株式)" ],
"html.fancy" : "html/fancy_table.html" ,
"html.screening" : "html/screening_table.html" ,
"template.detail" : "fancy_table.j2.html" ,
"template.table" : "screening_table.j2.html" ,
"nyears" : 15 ,
"nParallels" : 4 ,
"sort" : true ,
},
}
留意事項¶
本コードはyfinanceを使用しています.
免責事項¶
Note
本コードは、オープンソースライブラリ yfinance を利用して Yahoo! Finance から取得可能な金融データを参照する例を示したものです。
本コードは学習・研究・個人利用を目的としたものであり、投資助言や売買推奨を行うものではありません。
本コードが取得するデータは Yahoo! Finance に依存しており、データの正確性、完全性、最新性を保証するものではありません。
Yahoo! Finance のデータ利用については、各自で[Yahoo!利用規約](https://legal.yahoo.com/) をご確認ください。
本コードを利用した結果生じたいかなる損害・損失についても、作者は一切の責任を負いません。
公開されているコードは自由に改変・利用可能ですが、 取得したデータの再配布や商用利用については利用規約を遵守してください 。
本コードを利用する際は、必ず自己責任でご利用ください。