So-net無料ブログ作成
検索選択

決算発表予定をキャッシュ保存できるアプリ [データ共有]

*2017/1/26 コード修正しました。

注意:自己責任での利用をお願いします。
1)決算発表が集中して取得される銘柄数が極端に多い場合の挙動は今後の検証が必要です。 場合によっては無限ループエラーが出現することも考えられます。
(2017/1/26追記 3,400銘柄で問題ないようです)
(2)可能性は低いですが、キャッシュの保存と読み込みが衝突した場合ディスクエラー等重大エラーを起こすことも完全に否定はできません。


以前のアプリでは、データグリッド追加で時間がかかっています。
起動の短縮のために、タイマーを利用して決算予定を読み込む度に最大2秒間のインターバルを設定して、この間に決算が追加される他場合は続けて読み込んで、できるだけ一度にデータグリッドに追加するようにしています。

一つのニュースごとに、GlobalDictionaryにデータを追加します。
しかし、GlobalDictionary全データを追加した後にまとめてキャッシュにセーブするためにForループでGlobalDictionaryからデーターを呼び込む段階で極端に時間がかかり、無限ループエラーを起こしてしまいました。

仕方がないので、dicLocalというGlobalのクローンDictionaryを別に作成して、これにも同時にデータを追加してキャッシュ保存にはGlobalを利用せずにローカルのdicLocalをループで回してデータ保存しています。

決算発表シーズンになって、1つの決算予定のニュースに含まれる銘柄数が極端に多くなった場合、GlobalDictionaryに追加する過程でエラーが出る可能性は否定できません。
あるいは、前記事の分析テクニックでもキャッシュファイルを一度にGlobalDictionaryに追加しているため、こちらでエラーが出るかもしれません。


* まず、キャッシュファイルのディレクトリに間違いがないか確認してください。
32ビットWindowsであれば、「Program Files (x86)」ではないので必ず変更してください。
場合によっては、管理者権限エラーが実行時に出てしまう場合には、「C:\data\」など必ず既存のディレクトリを指定してください。

** トレーディングアプリケーションを新規作成してコードを追加したら、プロパティを開いてInitializedイベントに「AnalysisTechnique_Initialized」を選択してください。


アプリ起動後の流れは以下のようになります。

(1)アプリ起動で過去のニュースタイトル一覧が取得できなかった時は、右側のラベルに「News配信のチェックが必要です」と出力されます。
キャッシュがあればこれを読み込みに行って、データグリッドに表示します。最初はないので何も表示されません。
キャッシュが表示されると、「配信日」列で日付の確認が可能です。
この場合、「ニュース」タブにニュースが配信されているかを確認してください。
ここに、何も表示されていなければ、オリジナルのニュースアプリを起動して本文取得中で止まっていないか確認してください。トレステ再起動で取得できるようになることもあります。

(2)ニュースタイトル一覧は取得されるが、決算発表予定のニュースが見つからなかった場合、「決算発表予定のニュースが見つかりません」と表示されて、キャッシュを読み込みに行きます。
これは、時間帯によってよくあるようです。
決算予定のニュースは、早朝と夕方に配信され後場あたりで数時間配信されなくなるようです。
この配信されない時間の時にこのように表示されますが、配信されているにもかかわらずアプリだけタイトルが取得できていないこともあるようで、これも再起動で取得できることもあります。

(3)過去のニュースに決算発表があれば、右側のラベルに「決算発表予定の配信が見つかりました」と表示され、配信を読み込みます。
連続して読み込めるニュースがなくなたときに、データグリッドに追加が始まります。この時データグリッドのスクロールバーが動くのでわかると思います。
これは数秒かかります。

(4)データグリッドの追加が終了すると、「ボタンを押してCashをセーブしてください」と表示されボタンが押せる状態に変化します。
これで、ボタンを押すとキャッシュとして保存されます。
ボタンを押す前に、コードにまとまった抜けがないかを確認してください。
ニュース配信の間隔が2秒以上開いた場合には、追加されたニュースが処理されて一度全行をクリアして再度追加されると思います。

(5)起動したままにしておくと、配信が再開した時に自動的に読み込むと思っていたのですが、どうも配信再開のタイトル取得ができないようで、アプリの再起動が必要のようです。
朝起動してデータを取得あるいは朝夕で起動取得という方法が良さそうです。
取得できない場合には、諦めてしばらくはキャッシュを利用してください。

今後の検証で何か発見あれば修正します。


* このアプリを起動して「取得できました」以降にレーダースクリーンに決算発表の分析テクニックを追加すると最新のデータが表示されます。
** 普通は、データグリッドを表示したまま終了して再起動すると思います。
この場合、データグリッド起動の時点で古いキャッシュを読み込むため過去のデータがすでに表示されているはずです。
この状態でアプリでデータを取得した場合には、データグリッドを更新するかワークスペースの再起動が必要です。
データグリッドの再起動は、レーダースクリーン行番号の上の行列ヘッダをクリックして全行を選択した状態で、「表示メニュー」から一番下の「更新」をクリックします。

決算予定の更新.png



2017/1/26 コード修正しました。

(1)起動後、新たに決算発表予定の配信があっても取得できなかった不具合を修正できたと思う(自信なし)。
(2)オートセーブをデフォルトにしました。
(3)期日を過ぎた発表予定も削除しないようにしました(新たなデータあれば上書き)。
(4)コンピュータ名を取得して、保存フォルダを変更できるように工夫しました。
(5)その他。


//--------------------------------------------------------------------
//This code is created by Tradestation Developement Enviroment ver 9.5
//Copyright (c) 2017 nari
//Released under the MIT license
//http://opensource.org/licenses/mit-license.php
//--------------------------------------------------------------------

//トレーディングアプリケーションを新規作成してプロパティを開いて、Initializedイベントに「AnalysisTechnique_Initialized」を選択してください。
//
//変数のdir初期値を変更してください。
//複数のコンピュータでDropboxを保存場所に指定する場合には、59行のコメントを外してコンピューター名をそれぞれ取得して、
//それ以降のSwitch分岐を環境に合わせて変更してください。
//
//############ 注意 ################
//
//エラー処理をしているので重大なディスクエラーを起こす可能性は少ないと思っていますが、
//危険性がないとは言えませんのでリスクを承知で使用してください。
//
//起動時に保存済みのキャッシュを読み込みます。
//決算予定(コード順)ニュースが配信済みであれば、これを読み込みデータを更新して表示します。
//autoSave(true)で更新と同時にキャッシュを保存します。
//falseの場合はボタンを押して保存してください。
//*データグリッドのデータを手動で変更しても、変更を保存することはできません。
//
//



using platform;
using elsystem;
using elsystem.windows.forms;
using elsystem.collections;
using tsdata.marketdata;
using elsystem.io;

Variables: Form Form1(null)
	, string dir("C:\Program Files (x86)\TradeStation 9.5\Program\")	//環境によっては要変更
	, GlobalDictionary gDicKessan(null)
	, Dictionary dicLocal(null)		//GlobalDictionaryが遅いのでCashのセーブロード用
	, DataGridView dgvKessan(null), DataGridView dgvNews(null)
	, TabControl tab(null), TabPage page1(null), TabPage page2(null)
	, NewsProvider NewsProvider1(null)
	, Label Label1(null), Button btnSave(null), Button btnLoad(null)
	, Timer Timer1(null)
	, int mSecNewsUpdated(1000)		//起動初期にこれ以上経過すると過去の配信終了と判断(ミリ秒)
	, int mSecKessanNewsLoaded(2000) //決算発表予定を読み込んだ場合残りがないと判断するまでの時間
	, int mSecSaveCash(2000)		//データグリッドに決算予定一覧が表示されてからキャッシュを保存するまでの時間
	, bool autoSave(true)	//trueで決算予定を新規に取得できた時に保存もする
	, bool isPrev(true)		//最新データだけでなく過去データも保存する場合(false当日以前を切り捨て)
	, Dictionary dicKessanNews(null)
	;
Array: Quoter[7]("");

method void AnalysisTechnique_Initialized( elsystem.Object sender, elsystem.InitializedEventArgs args ) 
Variables: int counter, DateTime dd, int code, Vector vec;
begin
	Clearprintlog();
	Form1 = Form.Create();
	//print(elsystem.Environment.GetMachineName());	//これでコンピュータ名が印刷ログに出力される
	switch(elsystem.Environment.GetMachineName())
	Begin		//コンピュータ名でキャッシュ保存場所を変更
		case "PD81": dir = "\\Mac\Dropbox\trade\TradeStation\";
		Case "DESKTOP-CNNGMUI": dir = "C:\Users\B75\Dropbox\trade\TradeStation\";
	end;
	//決算の記号
	Quoter[1] = "本";
	Quoter[2] = "修";
	Quoter[3] = "①";
	Quoter[4] = "②";
	Quoter[5] = "③";
	Quoter[6] = "④";
	Quoter[7] = "⑤";	
	tab = TabControl.Create();
	Form1.AddControl(tab);
	tab.Dock = elsystem.windows.forms.DockStyle.Fill;
//----Tabpage1
	page1 = TabPage.Create();
	page1.Text = "決算予定";
	tab.AddControl(page1);
	dgvKessan = DataGridView.Create();
	dgvKessan.Height = 20;
	dgvKessan.Columns.Add("コード");
	dgvKessan.Columns.Add("name");
	dgvKessan.Columns.Add("予定日");
	dgvKessan.Columns.Add("期");
	dgvKessan.Columns.Add("配信日");
	dgvKessan.Columns.Add("dummy");
	dgvKessan.Columns[5].Visible = false;
	
	For counter = 0 to 4 begin
		dgvKessan.Columns[counter].AutoSizeMode = elsystem.windows.forms.DataGridViewAutoSizeColumnMode.AllCells;
	End;
	dgvKessan.ColumnHeadersDefaultCellStyle.Alignment = elsystem.windows.forms.DataGridViewContentAlignment.MiddleCenter;
	dgvKessan.Columns[2].DefaultCellStyle.Format = "yyyy/M/d";
	dgvKessan.AllowUserToAddRows = false;
	dgvKessan.Name = "dgvKessan";
	page1.AddControl(dgvKessan);
	dgvKessan.Dock = elsystem.windows.forms.DockStyle.Left;
	dgvKessan.Width = 530;
	tab.AddControl(page1);
	btnSave = Button.Create();
	btnSave.Text = "Save";
	btnSave.Location(550, 60);
	btnSave.Enabled = false;
	btnSave.Click += Button_Click;
	Label1 = Label.Create();
	Label1.AutoSize = true;
	Label1.Location(550, 3);
	page1.AddControl(Label1);
	page1.AddControl(btnSave);
	
//---------Tabpage2
	page2 = TabPage.Create();
	dgvNews = DataGridView.Create();
	page2.AddControl(dgvNews);
	dgvNews.Dock = elsystem.windows.forms.DockStyle.Fill;
	page2.Text = "ニュース";
	dgvNews.Columns.Add("date");
	dgvNews.Columns.Add("Title");
	dgvNews.Columns.Add("Source");
	dgvNews.Columns.Add("Summary");
	dgvNews.Columns.Add("Content");
	dgvNews.Columns[0].AutoSizeMode = elsystem.windows.forms.DataGridViewAutoSizeColumnMode.AllCells;
	dgvNews.Columns[1].Width = 360;
	dgvNews.AllowUserToAddRows = false;
	tab.AddControl(page2);
	
	dicLocal = Dictionary.Create();
//-------GlobalDictionary
	If gDicKessan = null then
	Begin
		gDicKessan = GlobalDictionary.Create(true, "kessan");
	End;
	If isPrev then LoadCash();	//過去データも蓄積する場合
	Label1.Text= "initializing wait...";
//-------NewsProvider
	NewsProvider1 = NewsProvider.Create();
	NewsProvider1.Updated += NewsProvider1_Updated;
	NewsProvider1.FilterType = 0;	//all
	//NewsProvider1.Keywords = "決算";	//絞込するとなぜか後で配信されても取得できない
	NewsProvider1.Load = true;
	//print("DaysBack:", NewsProvider1.DaysBack, " From:", NewsProvider1.From.ToString(), " TimeZone:", NewsProvider1.TimeZone.ToString());
	//DaysBack:  10.00 From:2017/01/10 TimeZone:local
	Form1.Dock = elsystem.windows.forms.DockStyle.Fill;
	Form1.FormClosing += Form1_FormClosing;
	Timer1 = Timer.Create();
	Timer1.Elapsed += Timer1_Elapsed;
	Timer1.Interval = mSecNewsUpdated;
	Timer1.AutoReset = false;
	Timer1.Enable = true;
	Timer1.Start();
	
	Form1.Show();
	
End;

Method void Form1_FormClosing( elsystem.Object sender, elsystem.windows.forms.FormClosingEventArgs args ) 
Begin
	print("form closing");
	SaveCash();	
End;


method void Timer1_Elapsed( elsystem.Object sender, elsystem.TimerElapsedEventArgs args ) 
begin
	Timer1.Stop();
	Timer1.Enable = false;
	Switch(Label1.Text)
	Begin
		Case "initializing wait...":
			If dgvNews.Rows.Count = 0 then
				Label1.Text = "News配信のチェックが必要です"
			Else
				Label1.Text = "決算発表予定のニュースが見つかりません";
			print("news not found");
			If dgvNews.Rows.Count > 0 then
				dgvNews.Sort(dgvNews.Columns[0], 1); //ListSortDirection  2ページ目のニュース一覧日付逆順で表示
			If dicLocal.Count = 0 then LoadCash();
			If dicLocal.Count > 0 then
				UpdateKessanDataGridview(true);
		Case "取得できました":
			If autoSave then
			Begin
				Label1.Text = "cash saving...";
				print("news readed");
				SaveCash();
			End
			Else
				Label1.Text = "ボタンを押してCashをセーブしてください";
			btnSave.Enabled = true;
		Case "決算発表予定の配信が見つかりました":
			print("update dgv by readed News");
			UpdateKessanDataGridview(false);
	End;
end;

Method void NewsProvider1_Updated( elsystem.Object sender, NewsUpdatedEventArgs args ) 
Vars: int lastRow, bool isEx, DateTime d1;
Begin
	If args.Data <> null then
	Begin
		args.Data.PublishDate.AddHours(9);	//日本時間に変更
		If args.Data.Title.Contains("決算") then
		dgvNews.rows.Add(args.Data.PublishDate, args.Data.Title
			, args.Data.SourceFeed.Description
			, args.Data.Summary,  args.Data.Content.Text);
		If dgvNews.Rows.Count > 0 then lastRow = dgvNews.Rows.Count - 1;
		If Form1.Tag = null then dgvNews.Rows[lastRow].Cells[1].ForeColor = elsystem.drawing.Color.Gray;

		If args.Data.Title.Contains("コード順") then
		Begin
			If dicKessanNews = null then dicKessanNews = Dictionary.Create();
			isEx = false;
			If dicKessanNews.Contains(args.Data.Title) then
			Begin
				If DateTime.TryParse(dicKessanNews[args.Data.Title].ToString(), d1) then
				Begin
					If args.Data.PublishDate > d1 then
						isEx = true;
				End;
			End;
			If isEx = false then
			Begin
				args.Data.Content.Load();	//記事の内容を読み込む
				dgvNews.rows.Add(args.Data.PublishDate, args.Data.Title
					, args.Data.SourceFeed.Description
					, args.Data.Summary,  args.Data.Content.Text);
				if args.Data.Content.State = 1 then //loaded
					AddKessan(args.Data.Title, args.Data.Content.Text, args.Data.PublishDate);
				
				//dictionaryのタイトル配信日と比較 新しければタイマーリセット
				If dicKessanNews.Contains(args.Data.Title) then
					dicKessanNews.Remove(args.Data.Title);
				dicKessanNews.Add(args.Data.Title, args.Data.PublishDate);
				timer1.Enable = false;
				Timer1.Interval = mSecKessanNewsLoaded;
				print(args.Data.Title, " timer reset ", Timer1.Interval);
				Timer1.Start();
			End;
		End
		Else
		Begin
			If Form1.Tag = null then
			Begin
				Timer1.Enable = false;
				print("timer reset ", Timer1.Interval, "  ewws:", args.Data.PublishDate.ToString(), " ", args.Data.Title);
				Timer1.Start();
			End
			Else
			dgvNews.Sort(dgvNews.Columns[0], 1); //ListSortDirection  2ページ目のニュース一覧日付逆順で表示
		End;	
	End;
End ;


Method void SaveCash()
Vars: StreamWriter writer, string lines, int counter, string key;
Begin
	btnSave.Enabled = false;
	If dicLocal.Count > 0 then
	Begin;
		For counter = 0 to dicLocal.Count - 1 Begin
			key = dicLocal.Keys[counter].ToString();
			lines += key;
			lines += ",";
			lines += dicLocal[key].ToString();
			lines += NewLine;
		End;
		try
 			writer = StreamWriter.Create(dir + "kscash.da0");
			writer.Write(lines);
			Label1.Text = DateTime.Now.ToString() + NewLine + "キャッシュを保存しました";
		catch (elsystem.exception ex)
			Label1.Text = DateTime.Now.ToString() + NewLine + ex.Message.ToString();
		finally 
			writer.Close();
		end;
	End;
	btnSave.Enabled = true;
	
End;

Method void LoadCash()
Vars: bool isFileExist, StreamReader reader, string line, Vector vec, DateTime dMax;
Begin
	Try
		reader = StreamReader.Create(dir + "kscash.da0");
		isFileExist = true;
	Catch(elsystem.io.FileNotFoundException ex)
		print("kessan cash file not found");
	end;
	If isFileExist then
	Begin
		Label1.Tag = Label1.Text;
		Label1.Text = "Cashを読み込んでいます";
		Try
		Repeat
			line = reader.ReadLine();
			If line = "" then break;
			If line.Contains(",") = false then break;
			vec = line.Split(",");
			dicLocal.Add(vec.at(0).ToString(), line.Replace(vec.at(0).ToString() + ",", ""));
			If dMax = null then
				dMax = DateTime.Parse(vec.at(4).ToString())
			Else
			Begin
				If DateTime.Parse(vec.at(4).ToString()) > dMax then dMax = DateTime.Parse(vec.at(4).ToString());
			End;
				
			If gDicKessan.Contains(vec.at(0).ToString()) = false then
				gDicKessan.Add(vec.at(0).ToString(), vec.at(2).ToString() + " " + vec.at(3).ToString());
		Until (reader.EndOfStream);
		Catch(elsystem.io.IOException ex)
			print("cash reading error");
			print(ex.Message);
		Finally
			reader.Close();	
		End;
		print(DateTime.Now.ToString(), " kessan cash loaded by ShowMe @g決算Cash ", );	
		
	End;
	If dMax <> null then Form1.Tag = dMax;
End;

Method void AddKessan(string title, string content, DateTime dPub)
Vars: 
	 int pos1, int pos2
	, string sCode, string name, string sDate, string sQ
	, int counter, bool ex
	, string line, Vector vec
	, int targetRow
	, int code, int yy, int mm, int dd, DateTime dKessan;
Begin
	If content.Length > 0 then
	Begin
		Label1.Text = "決算発表予定の配信が見つかりました";
		targetRow = dgvKessan.Rows.Count;
		dgvKessan.Rows.Add(-1, "reading..", "", "", dPub);
		dgvKessan.Columns[2].Visible = false;
		dgvKessan.Columns[3].Visible = false;
		
		pos1 = content.IndexOf("<");
		While pos1 > 0 Begin
			pos2 = content.IndexOf(">", pos1);	//codeは<>でくくられている
			If pos2 < pos1 then break;
			sCode = content.Substring(pos1 + 1, pos2 - pos1 - 1);
			pos1 = content.IndexOf("(", pos2);	//銘柄名に続き()の中に発表日と期
			If pos1 < pos2 then break;
			name = content.Substring(pos2 + 1, pos1 - pos2 - 1);
			pos2 = content.IndexOf(")", pos1);
			If pos2 < pos1 then break;
			sDate = content.Substring(pos1 + 1, pos2 - pos1 - 1);
			If int.TryParse(sCode, code) then
			Begin
				sDate = SDate.Replace("本", "/本").Replace("修", "/修").Replace("①", "/1Q")
					.Replace("②", "/2Q").Replace("③", "/3Q").Replace("④", "/4Q").Replace("⑤", "/5Q");
			
				vec = sDate.Split("/");
				mm = int.Parse(vec.at(0).ToString());
				dd = int.Parse(vec.at(1).ToString());
				//年末の日付に対応するため  ニュースの決算予定には過去分がないと仮定
				If mm < DateTime.Now.Month then 
					yy = DateTime.Now.Year + 1 
				else 
					yy = DateTime.Now.Year;
				dKessan = DateTime.Create(yy, mm, dd);
				
				sDate = dKessan.ToString() + " " + vec.at(2).ToString();
				
				If gDicKessan.Contains(sCode) then
				Begin
					If gDicKessan[sCode].ToString() <> sDate then
					Begin
						gDicKessan[sCode] = sDate;	//上書き
					End;
				End
				Else
				Begin
					gDicKessan.Add(sCode, sDate);
				End;
				line = name + "," + dKessan.ToString() + "," + vec.at(2).ToString() + "," + dPub.ToString();
				//localにも追加
				If dicLocal.Contains(sCode) then
					dicLocal[sCode] = line
				Else
					dicLocal.Add(sCode, line);
				
			End;
			pos1 = content.IndexOf("<", pos2);
			If pos1 < pos2 then break;	//"<"がみつからなければ抜ける
		End; 
		If dgvKessan.Rows.count > 0 then
		Begin
			dgvKessan.Rows[targetRow].Cells[0].Value = code;
			dgvKessan.Rows[targetRow].Cells[1].Value = title;
		End;
		If Form1.Tag <> null then
		Begin
			If dPub > DateTime.Parse(Form1.Tag.ToString()) then Form1.Tag = dPub;
		end;
	End;
End;

Method void UpdateKessanDataGridview(bool isCash)
Vars: int counter, int code, DateTime dd, DateTime update, Vector vec, string str, string str2;
Begin
	If dicLocal.Count > 0 then
	Begin
		If isCash = false then
			dgvKessan.Rows[0].Cells[1].Value = "データグリッドに追加中.. お待ちください";
		dgvKessan.Columns[2].Visible = true;
		dgvKessan.Columns[3].Visible = true;
		Form1.SuspendLayout();	//描画中断 少しでも早くなる?
		dgvKessan.Rows.Clear();
		For counter = 0 to dicLocal.Count - 1
		Begin
			code = int.Parse(dicLocal.Keys[counter].ToString());
			vec = dicLocal.Values[counter].ToString().Split(",");
			dd = DateTime.Parse(vec.at(1).ToString());
			update =  DateTime.Parse(vec.at(3).ToString());
			If dd > DateTime.Now - TimeSpan.Create(1, 0, 0, 0) then
				dgvKessan.Rows.Add(code, vec.at(0).ToString(), dd, vec.at(2).ToString(), update, dd)
			Else
			dgvKessan.Rows.Add(code, vec.at(0).ToString(), dd, vec.at(2).ToString(), update, dd + TimeSpan.Create(365, 0, 0, 0));
		End;
		Form1.ResumeLayout();
		If isCash then
		Begin
			If Form1.Tag <> null then
				str2 = "最終更新:" + Form1.Tag.ToString(); 
			str = Label1.Tag.ToString();
			Label1.Text = str + NewLine + NewLine + "Cashを読み込みました" + NewLine + str2;
		End
		Else
		Begin
			Label1.Text = "取得できました";
			Timer1.Interval = mSecSaveCash;
			Timer1.Enable = true;
			Timer1.Start();
		End;
		dgvKessan.Sort(dgvKessan.Columns[5], 0);
	End;
End;

method void Button_Click( elsystem.Object sender, elsystem.EventArgs args ) 
vars: int counter;
begin
	Switch((sender AsType Button).Text)
	Begin
	Case "Save":
		If dgvKessan.Rows.Count > 0 then
		Begin
			Label1.Text = "saving...";
			SaveCash();
		End;
	End;
end;



充分に検証していないため、今後変更していく可能性があります。
nice!(0)  コメント(0)  トラックバック(0) 
共通テーマ:

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。