上記の記事のつづきで、グラフをマクロで作成していきます。
作成するグラフは、下記になります。
保有銘柄の損益グラフです。棒グラフが銘柄別の損益で、折れ線グラフが全株の損益を表しています。損益の計算は、「( 株価 - 取得単価 )×持ち株数」で計算しているため、取得単価、持ち株数の入力は必要です。
上の図は、1か月程度のグラフです。あまり日数が多いと見にくかったので、少な目にしました。
エクセルのトップ画面「株価一覧」です。前回の記事からボタンを2つ追加しています。
「データ登録」ボタンを押すと、データシートに今日の株価(取得済みの株価)を転記します。
データシートに銘柄名が存在しなければ、項目を作成します。このとき、グラフにも要素の追加を行います。データがない状態であっても、グラフとセルのリンクは行えるので、データが入る前にグラフとセルのリンクをあらかじめ作成しています。
このデータシートの5行目から30行目までのデータがグラフに表示されるようにリンクしているため、行挿入を行うとグラフの参照範囲がずれてしまうために、ソートを利用しています。
新しい株価や損益の情報は、一度末尾に追加後に日付の降順ソートをかけているので、最新の日付が上にきます。古くなった日付は、グラフ範囲の外に追いやられていきますが、削除はおこなっていません。データベースをイメージしているので、ひたすらデータを貯めています。不要な方は適宜消してください。
保有銘柄から外れた銘柄は、マクロではあまり気にしていません。データシートでの該当列の削除と、グラフから削除は、手動になります。
グラフを右クリックして、データの選択をクリックすると、下記のダイアログが表示されるため、不要になった銘柄を選択して削除してください。
上の図に表示されている全株は、マクロでの設定はしていません。初期設定として、手動で設定しています。間違えて削除した場合は、手動で再登録してください。
「グラフ保存」ボタンは、グラフを別ファイルへ保存します。マクロ有効ブックと同階層に「Graph」フォルダを作成し、年数の数字をファイル名にし、グラフのシート名を月数にしています。(例)「株価一覧」の株価取得日が「2023/6/10」のときに、「グラフ保存」ボタンを押すと「Graph\2023.xlsx」に持ち株損益グラフがコピーされ、シート名が「06」になります。グラフは、リンクを外した、直値グラフになります。「グラフの保存」ボタンは月に何度も押すと、シート名が重なってしまうため、ひと月に1度以上押さないでください。
毎日データを貯める想定ですが、取得しそびれたり、まとめてグラフを作りたい場合は、データシートに直接記入してください。1行目~3行目以外は、手動でデータを記入してもらっても大丈夫です。損益の計算にエクセル関数を使用しても問題ありません。作業中の注意事項は、前述にも記述していますが、グラフとのリンクがくずれるため、行挿入や行追加は使用しないでください。また、B列は削除しないでください。
まとめて過去の株価をCSVで取得したい場合は、investing.comにて無料で取得可能です。ただし、ログイン(アカウント)が必要になります。ページのトップで、銘柄コードで検索します。日本株以外もヒットする場合は、選択肢が表示されるため、日の丸の国旗を選べば、日本株を表示させられます。銘柄のページで「過去のデータ」を選択すれば、カレンダーにて取得範囲を選ぶことができます。ちなみにこのサイトはスクレイピング禁止サイトのため、マクロでの取得はできません。
別シートへ書き出すため、主に、標準モジュールに記述します。
それにあわせて、前回、株価一覧のシートオブジェクトに記述していた関数群も標準モジュールに移します。
====================================
' 固定位置の設定
'====================================
Function 一覧Sht() As Worksheet
Set 一覧Sht = Worksheets("株価一覧")
End Function
' ----------------------------
' 取得日セル Range型
' ----------------------------
Function 取得日セル() As Range
Set 取得日セル = 一覧Sht.Range("D1")
End Function
' ----------------------------
' 項目開始行数
' ----------------------------
Function 開始行数() As Long
開始行数 = 3
End Function
' ----------------------------
' 銘柄Code先頭セル Range型
' ----------------------------
Function CodeTopRng() As Range
Set CodeTopRng = 一覧Sht.Range("B" & 開始行数)
End Function
' ----------------------------
' 表末尾の行位置(行数)を取得
' ----------------------------
Function 最終行数() As Long
'テーブル末尾(銘柄Codeが空欄)まで
If CodeTopRng.Offset(1, 0).Value <> "" Then
'2つ以上銘柄がある場合
最終行数 = CodeTopRng.End(xlDown).Row
Else
'先頭が最終行
最終行数 = CodeTopRng.Row
End If
End Function
Function 持ち株数列() As Long
持ち株数列 = 一覧Sht.Range("E2").Column
End Function
Function 取得単価列() As Long
取得単価列 = 一覧Sht.Range("F2").Column
End Function
' ----------------------------
' 銘柄Code列のセルすべて
' ----------------------------
Function Code列Rngs() As Range
'開始行~最終行までのB列情報を返す
Set Code列Rngs = 一覧Sht.Range("B" & 開始行数 & ":B" & 最終行数)
End Function
' ----------------------------
' 銘柄名列のセルすべて
' ----------------------------
Function 銘柄名列Rngs() As Range
'開始行~最終行までのC列情報を返す
Set 銘柄名列Rngs = 一覧Sht.Range("C" & 開始行数 & ":C" & 最終行数)
End Function
' ----------------------------
' 株価列のセルすべて
' ----------------------------
Function 株価列Rngs() As Range
'開始行~最終行までのD列情報を返す
Set 株価列Rngs = 一覧Sht.Range("D" & 開始行数 & ":D" & 最終行数)
End Function
上記は、前記事東証株 - 今日の株価を取得するエクセルマクロでシートオブジェクトに記述していたものを標準モジュール用に変更を加えています。具体的には、Rangeの前にシートオブジェクトを挿入して、シートを指定しています。
Function データSht() As Worksheet
Set データSht = Worksheets("データ")
End Function
Function DB銘柄先頭() As Range
Set DB銘柄先頭 = データSht.Range("C1")
End Function
Function DB最終列数() As Long
DB最終列数 = データSht.Cells(1, データSht.Columns.Count).End(xlToLeft).Column
If DB最終列数 >= 3 Then
'先頭以外の銘柄は、列を2つずらす
DB最終列数 = DB最終列数 + 2
Else
'先頭の銘柄は、全株の隣
DB最終列数 = DB最終列数 + 1
End If
End Function
Function DB日付入力列() As Long
'A列
DB日付入力列 = 1
End Function
Function DB全株列() As Long
'B列
DB全株列 = 2
End Function
Function DB日付開始行数() As Long
DB日付開始行数 = 5
End Function
Function DBグラフ表示末尾行数() As Long
DBグラフ表示末尾行数 = 30
End Function
Function DB最終行数() As Long
DB最終行数 = データSht.Cells(Worksheets(1).Rows.Count, DB日付入力列).End(xlUp).Row
End Function
データシート用の関数群。標準モジュールは、どの標準モジュールに記述してもオブジェクト名を省略して、関数名だけで呼び出せますが、どちらのシート向けの関数なのかがわからなくならないように管理する必要があります。前回作成したものは名前をそのままに、新たに追加する関数には頭に「DB」をつけています。(データベースの略)
標準モジュールにボタン押下時の処理を記述することはできますが、どこに記述したか迷子にならないように、シートオブジェクトのボタンなので、シートオブジェクトに入口だけ記載しておきます。中身は、標準モジュールに記述していきます。
下記のコードのみ、ActiveXボタンのついているシートオブジェクトに記述する。
ActiveXボタンのプロパティから、オブジェクト名を「データ登録」に変更すると、連動して下記のデータ登録_Clickを呼び出してもらえます。
フォームコントロールのボタンを使用したい方は、Privateを削除して登録してください。
Private Sub データ登録_Click()
Call 保有資産保存
End Sub
以降は、標準モジュールに記述する。
-----------------------------------
' 今日の保有資産をデータベースへ保存
' -----------------------------------
Sub 保有資産保存()
Dim dCell As Range 'ループ用(株価一覧シートの銘柄順)
Dim SearchRange As Range 'データシートの銘柄探索範囲
Dim findCell As Range 'Findメソッドの結果
Dim wrtRow As Long 'データシートの書き込み位置
Dim SortRanges As Range 'データシートのソート範囲
'データシートの値設定用
Dim 株価 As Long
Dim 持ち株 As Long
Dim 取得単価 As Long
Dim 全株損益計算 As Long
'初期化
全株損益計算 = 0
Call 保存Sht_銘柄名更新
'------------------------------------
' 今日の株価を保存する
'------------------------------------
'書き込む行を見つける
wrtRow = DB最終行数 + 1
'取得日を保存
データSht.Cells(wrtRow, DB日付入力列).Value = 取得日セル.Value
'データシートの銘柄項目行(一行すべて)を探す検索範囲とする
Set SearchRange = データSht.Rows(DB銘柄先頭.Row)
For Each dCell In 銘柄名列Rngs
Set findCell = SearchRange.Find(dCell.Value, LookAt:=xlPart)
If Not findCell Is Nothing Then
'格納する列(銘柄名)を見つけた
'株価の設定
株価 = 一覧Sht.Cells(dCell.Row, 株価列Rngs.Column).Value
データSht.Cells(wrtRow, findCell.Column).Value = 株価
'損益の計算準備(取得単価と持ち株)
取得単価 = 一覧Sht.Cells(dCell.Row, 取得単価列).Value
持ち株 = 一覧Sht.Cells(dCell.Row, 持ち株数列).Value
'損益の計算(株価 - 取得単価)*持ち株数
データSht.Cells(wrtRow, findCell.Column + 1) = (株価 - 取得単価) * 持ち株
'全株損益を求めるために損益を加算
全株損益計算 = 全株損益計算 + データSht.Cells(wrtRow, findCell.Column + 1)
End If
Next
'全株の損益計算結果格納
データSht.Cells(wrtRow, DB全株列).Value = 全株損益計算
'並べ替え範囲の設定
With データSht.UsedRange
Set SortRanges = データSht.Range(.Item(DB日付開始行数, 1), .Item(.Count))
'Debug.Print SortRanges.Address
End With
With データSht.Sort
'初期化
.SortFields.Clear
.SetRange SortRanges
.SortFields.Add SortOn:=xlSortOnValues, Key:=データSht.Range("A" & DB日付開始行数), Order:=xlDescending
.Header = xlNo
.Apply
End With
'画面遷移OFF
Application.ScreenUpdating = False
'ソート範囲選択解除
データSht.Activate
データSht.Range("A1").Select
一覧Sht.Activate
'画面遷移ON
Application.ScreenUpdating = True
'メモリ解放
Set dCell = Nothing
Set SearchRange = Nothing
Set findCell = Nothing
Set SortRanges = Nothing
End Sub
保存Sht_銘柄名更新を呼び出して、追加が必要であればデータシートに銘柄の列を追加し、グラフにも登録します。
Find関数を使用して、銘柄名の列を特定し、最終行に、日付、株価、損益を記入していきます。
ソート範囲として、日付の入力が開始するまでの上の行数(上4行)を外して、ソート範囲を設定します。ソートキーは、日付で、降順(xlDescending)を設定しています。
ソートを使用すると、ソート範囲が選択状態になるため、その解除処理を行っています。
Private Sub 保存Sht_銘柄名更新()
Dim SearchRange As Range
Dim dCell As Range
Dim findCell As Range
Dim 銘柄Cell As Range
Dim col As Long '列数
'保有資産管理シートの銘柄項目行(一行すべて)を探す検索範囲とする
Set SearchRange = データSht.Rows(DB銘柄先頭.Row)
For Each dCell In 銘柄名列Rngs
Set findCell = SearchRange.Find(dCell.Value, LookAt:=xlPart)
If Not findCell Is Nothing Then
'銘柄登録済み
col = findCell.Column
Else
'見つからない(追加が必要)
'最終列の末尾に追加
col = DB最終列数
データSht.Cells(DB銘柄先頭.Row, col).Value = 一覧Sht.Cells(dCell.Row, CodeTopRng.Column).Value & dCell.Value
'隣の列とセル結合
データSht.Range( _
データSht.Cells(DB銘柄先頭.Row, col), _
データSht.Cells(DB銘柄先頭.Row, col + 1)).Merge
'文字列の設定
データSht.Cells(DB銘柄先頭.Row + 1, col).Value = "持ち株数"
データSht.Cells(DB銘柄先頭.Row + 2, col).Value = "取得単価"
データSht.Cells(DB銘柄先頭.Row + 3, col).Value = "株価"
データSht.Cells(DB銘柄先頭.Row + 3, col + 1).Value = "損益"
Call グラフへ銘柄登録(col)
End If
'登録データが異なるなら(一覧の値で更新する)
'持ち株数の設定
If Not データSht.Cells(DB銘柄先頭.Row + 1, col + 1).Value = 一覧Sht.Cells(dCell.Row, 持ち株数列).Value Then
データSht.Cells(DB銘柄先頭.Row + 1, col + 1).Value = 一覧Sht.Cells(dCell.Row, 持ち株数列).Value
End If
'取得単価の設定
If Not データSht.Cells(DB銘柄先頭.Row + 2, col + 1).Value = 一覧Sht.Cells(dCell.Row, 取得単価列).Value Then
データSht.Cells(DB銘柄先頭.Row + 2, col + 1).Value = 一覧Sht.Cells(dCell.Row, 取得単価列).Value
End If
Next
End Sub
株価一覧の銘柄数分ループし、データシートに存在するか確認します。存在しない場合に追加し、グラフへ銘柄登録を呼び出すことで、グラフへ新しい銘柄を設定します。
また、持ち株数の変更や取得単価に変更があれば、データシートの情報を更新します。
'データシートの列数情報を受け取る
Sub グラフへ銘柄登録(col As Long)
Dim GraphSht
Dim GraphChart As Chart
Dim cnt As Long 'グラフ要素数
'グラフのシートオブジェクト取得
Set GraphSht = Sheets("持ち株損益")
GraphSht.Activate
'グラフ情報を変数に設定
Set GraphChart = GraphSht.Application.ActiveChart
'グラフに要素の追加
GraphChart.SeriesCollection.NewSeries
'登録済み銘柄数を取得
cnt = GraphSht.SeriesCollection.Count
GraphSht.FullSeriesCollection(cnt).Name = "=" & データSht.Name & "!" & データSht.Cells(DB銘柄先頭.Row, col).Address
'損益の追加
GraphSht.FullSeriesCollection(cnt).Values = データSht.Range(データSht.Cells(DB日付開始行数, col + 1), データSht.Cells(DBグラフ表示末尾行数, col + 1))
'横軸(日付)
GraphSht.FullSeriesCollection(cnt).XValues = データSht.Range(データSht.Cells(DB日付開始行数, DB日付入力列), データSht.Cells(DBグラフ表示末尾行数, DB日付入力列))
'グラフタイプに棒グラフを設定
GraphSht.FullSeriesCollection(cnt).ChartType = xlColumnClustered
End Sub
通常のシートオブジェクトはWorkSheet型ですが、グラフシートは、WorkSheetではないため、Sheets()でグラフシートオブジェクトを取得します。
GraphChart.SeriesCollection.NewSeriesを実行するとグラフの項目が増え、GraphSht.SeriesCollection.Countが更新されます。Nameにデータシートの銘柄名が入っているセルのアドレスを渡します。Valuesにデータシートの銘柄の損益情報を範囲を指定して渡し、Xvaluesにデータシートの日付セル情報の範囲を指定して渡します。
グラフには、FullSeriesCollectionとSeriesCollectionがありますが、セルが非表示でも表示するグラフがFullSeriesCollectionで、セルを非表示にすると表示しないグラフがSeriesCollectionです。
DB日付開始行数やDBグラフ表示末尾行数は、関数群に入っている関数です。
グラフに使用する範囲は、DBグラフ表示末尾行数で30行目までとしています。値を変更した場合、適応されるのは、新規に登録する銘柄のみになります。既にグラフに表示されている銘柄に関しては修正されませんので、手動での変更になります。
ボタン押下時の処理なので、シートオブジェクトに記載しています。
Private Sub OutPutBtn_Click()
Dim Wb As Workbook
Dim wbname As String
Dim shname As String
Dim rslt As Long
'グラフフォルダを作成(なければ作る)
If Dir(ThisWorkbook.path & "\" & "Graph", vbDirectory) = "" Then
MkDir ThisWorkbook.path & "\" & "Graph"
End If
'年ファイルを作成
wbname = Format(取得日セル.Value, "yyyy")
wbname = ThisWorkbook.path & "\" & "Graph\" & wbname & ".xlsx"
'シート名(取得日セルの月)
shname = Format(取得日セル.Value, "mm")
'ファイルを開く(なければ作る)
If Dir(wbname) = "" Then
Set Wb = OpenNewBook(wbname)
Else
Set Wb = OpenBook(wbname)
End If
'同じシート名がある
If IsSameSheetName(Wb, shname) = True Then
rslt = MsgBox("同一シート名が存在します。差し替えますか?", vbYesNo)
If rslt = vbYes Then
Application.DisplayAlerts = False
Wb.Sheets(shname).Delete
Application.DisplayAlerts = True
Else
Exit Sub
End If
End If
'シートをコピーしてリネーム
Call CopyGraphSheet_BooktoBook(ThisWorkbook, "持ち株損益", Wb, shname)
'直値グラフに変更
Call リンク解除(Wb, "" & shname)
Wb.Save
Wb.Close
End Sub
マクロ有効エクセルファイルと同階層にGraphフォルダが存在するか確認し、存在しなければ作成します。
株価取得日(D1セル)を元に、年月を取得し、その年数のエクセルファイルがGraphフォルダに存在するか確認します。ファイルがなければ作成し、開きます。ファイルがすでに存在すれば、開きます。
呼び出している関数IsSameSheetNameですでに存在するシート名かを確認しています。
シート名が被った場合は、メッセージボックスにて確認します。
シート名が被らなければ、または、解消されていれば、グラフシートをコピーし、リネームします。
呼び出している関数CopyGraphSheet_BooktoBookでシートをコピーしリネームしています。
呼び出している関数リンク解除で、グラフがデータシートを参照するリンクを解除し、直値データのグラフとします。
グラフを追加したエクセルブックを保存して、閉じます。
Sub リンク解除(Wb As Workbook, shtName As String)
Dim srs As Series
Dim GraphChart As Chart
Dim GraphSht
Dim val
'グラフシートの取得
Set GraphSht = Sheets(shtName)
'グラフシートのチャート情報を更新する
Set GraphChart = GraphSht.Application.ActiveChart
For Each srs In GraphChart.SeriesCollection
With srs
.Name = .Name
'日付フォーマット(和暦)
For Each val In .Values
val = Format(val, "[$-ja-JP]ge.m.d:@")
Next
.Values = .Values
End With
Next
For Each srs In GraphChart.FullSeriesCollection
With srs
.Name = .Name
'日付フォーマット(和暦)
For Each val In .Values
val = Format(val, "[$-ja-JP]ge.m.d:@")
Next
.Values = .Values
End With
Next
End Sub
グラフには、FullSeriesCollectionとSeriesCollectionがありますが、セルが非表示でも表示するグラフがFullSeriesCollectionで、セルを非表示にすると表示しないグラフがSeriesCollectionです。今回は、FullSeriesCollectionを使用していますが、両方載せています。なければ、ループを回らないだけです。
グラフがセルを参照していた状態から、直値を使用するように値の設定をしています。日付は、和暦フォーマットを使用しています。
**********************************************************************
' エクセルブック内に同一シート名が存在するかを返す
' 第一引数:エクセルブック(Workbook)、第二引数:シート名(String)
' 戻り値:TRUE(同一シート名あり)
' FALSE(同一シート名なし)
'**********************************************************************
Function IsSameSheetName(book As Workbook, chkSheetName As String) As Boolean
Dim targetSheet
'戻り値を同一シート名なしで初期化
IsSameSheetName = False
'引数のブック内のシート数分確認
For Each targetSheet In book.Sheets
If Not chkSheetName = "" Then
'シート名を比較
If StrComp(chkSheetName, targetSheet.Name, vbTextCompare) = 0 Then
'同一シート名ありを設定
IsSameSheetName = True
Exit For
End If
Else
'チェックなし
Exit For
End If
Next
'メモリの解放
Set targetSheet = Nothing
End Function
IsSameSheetNameは、以前作成したものですが、通常のシートは、WorkSheet型に対して、グラフのシートは該当しないため、Sheetsでループするように変更しています。
**********************************************************************
' エクセルシートの複製処理
' (コピー元シート:inWorkbook、コピー先:outWorkbook)
'
' 外部ファイル(inWorkbook)ファイル内の
' シート名(copySheet)を outWorkbookの先頭へ複製
' コピー後のシート名を(outSheetname)にリネーム
' 戻り値:複製されたシートオブジェクト
'**********************************************************************
Function CopyGraphSheet_BooktoBook(ByVal inWorkbook As Workbook, _
ByVal copySheet As String, _
ByVal outWorkbook As Workbook, _
Optional ByVal outSheetName As String = "") As Object
Dim searchSheet
Dim result As Boolean
If Not inWorkbook Is Nothing And Not outWorkbook Is Nothing Then
For Each searchSheet In inWorkbook.Sheets
If searchSheet.Name = copySheet Then
Exit For
End If
Next
If Not searchSheet Is Nothing Then
'シートを複製
inWorkbook.Sheets(copySheet).Copy Before:=outWorkbook.Sheets(1)
'複製されたシートオブジェクトを返す
Set CopySheet_BooktoBook = outWorkbook.ActiveSheet
If Not outSheetName = "" Then
'オプションでリネーム指示あり
'同一シート名が存在するか確認
result = IsSameSheetName(outWorkbook, outSheetName)
If result = False Then
'変更したいシート名と同一シート名がない場合、リネームする
'複製シートに指定の名前を付ける
outWorkbook.ActiveSheet.Name = outSheetName
Else
End If
End If
End If
End If
'メモリ解放
Set searchSheet = Nothing
End Function
IsSameSheetNameと同じく、CopyGraphSheet_BooktoBookは、以前作成したCopySheet_BooktoBookがベースですが、グラフのシートはWorkSheetに該当しないため、Sheetsで扱うように変更しています。その際、関数名にGraphを追加しました。(流用のため、シート名被りのチェックが再度入っています。今回はチェック済みのため、ここでシート名被りのルートには入りません。)
ダウンロードしたエクセルファイルを起動し、コンテンツの有効化を行う。
マクロの有効化手順については、こちらも参考にしてください。