• R/O
  • SSH
  • HTTPS

protra: Commit


Commit MetaInfo

Revision521 (tree)
Time2014-04-23 00:16:42
Authorpanacoran

Log Message

#24689 Yahoo!ファイナンスから複数日の株価データをまとめて取得する

* Protra.Lib/Update/YahooFinanceUpdator.cs (YahooFinanceUpdator.Update): スレッド数を2に増やす。上場廃止のコードを取り除く。複数日の株価データをまとめて取得する。
(YahooFinanceUpdator.RunFetchPrices): DoFetchPriceから改名。複数日の株価データを扱う。
(YahooFinanceUpdator.FetchPrices): 新規。データ取得をDoFetchPriceから分離する。
(YahooFinanceUpdator.GetPage): 日付を引数で取る。
(YahooFinanceUpdator.ParsePage): 日付の列を引数に取る。複数日の株価データを返す。

Change Summary

Incremental Difference

--- protra/trunk/ChangeLog.txt (revision 520)
+++ protra/trunk/ChangeLog.txt (revision 521)
@@ -1,3 +1,13 @@
1+2014-04-22 panacoran <panacoran@users.sourceforge.jp>
2+
3+ #24689 Yahoo!ファイナンスから複数日の株価データをまとめて取得する
4+
5+ * Protra.Lib/Update/YahooFinanceUpdator.cs (YahooFinanceUpdator.Update): スレッド数を2に増やす。上場廃止のコードを取り除く。複数日の株価データをまとめて取得する。
6+ (YahooFinanceUpdator.RunFetchPrices): DoFetchPriceから改名。複数日の株価データを扱う。
7+ (YahooFinanceUpdator.FetchPrices): 新規。データ取得をDoFetchPriceから分離する。
8+ (YahooFinanceUpdator.GetPage): 日付を引数で取る。
9+ (YahooFinanceUpdator.ParsePage): 日付の列を引数に取る。複数日の株価データを返す。
10+
111 2014-04-18 panacoran <panacoran@users.sourceforge.jp>
212
313 PriceDataUpdatorの営業日についてのループを簡略化する
--- protra/trunk/Protra.Lib/Update/YahooFinanceUpdator.cs (revision 520)
+++ protra/trunk/Protra.Lib/Update/YahooFinanceUpdator.cs (revision 521)
@@ -35,12 +35,13 @@
3535 public class YahooFinanceUpdator : PriceDataUpdator
3636 {
3737 private readonly Object _syncObject = new object();
38- private Queue<Brand> _brandQueue;
39- private DateTime _date;
40- private readonly Queue<Price> _priceQueue = new Queue<Price>();
38+ private Queue<string> _codeQueue;
39+ private readonly List<DateTime> _series = new List<DateTime>();
40+ private readonly Queue<IEnumerable<Price>> _priceQueue = new Queue<IEnumerable<Price>>();
4141 private bool _terminate;
4242 private Exception _exception;
4343 private readonly Progress _progress = new Progress();
44+ private const int DaysAtOnce = 50; // 一度に取得する時系列の営業日数
4445
4546 /// <summary>
4647 /// データが存在する最初の日付を取得する。
@@ -64,26 +65,46 @@
6465 // 新しいデータが置かれるのは早くても午後7時以降
6566 if (end.Hour < 19)
6667 end = end.AddDays(-1);
67- var threads = new Thread[1];
68+ var threads = new Thread[2];
6869 for (var i = 0; i < threads.Length; i++)
69- (threads[i] = new Thread(DoFetchPrice) {Name = "Fetch Thread " + i}).Start();
70+ (threads[i] = new Thread(RunFetchPrices) {Name = "Fetch Thread " + i}).Start();
71+ var codes = new List<string>();
72+ foreach (var brand in GlobalEnv.BrandData)
73+ if ((brand.Flags & Brand.Flag.OBS) == 0)
74+ codes.Add(brand.Code);
7075 try
7176 {
7277 var dates = ListOpenDates(begin, end);
73- _progress.NumDays = dates.Count;
74- _progress.RecordsPerDay = GlobalEnv.BrandData.Count;
78+ _progress.NumDays = (dates.Count + DaysAtOnce - 1) / DaysAtOnce;
79+ _progress.RecordsPerDay = codes.Count;
7580 _progress.Start();
76- foreach (var date in dates)
81+ do
7782 {
83+ _progress.Show(worker, dates[0]);
84+ // 日経平均の時系列データの存在を確認する。
85+ var n = Math.Min(DaysAtOnce, dates.Count);
86+ var nikkei225 = FetchPrices("1001", dates.GetRange(0, n));
87+ dates.RemoveRange(0, n);
88+ _series.Clear();
89+ foreach (var price in nikkei225)
90+ {
91+ if (price.Close == 0)
92+ break;
93+ PriceData.Add(price, false);
94+ _series.Add(price.Date);
95+ }
96+ if (_series.Count == 0)
97+ return;
98+ var lastDate = _series[_series.Count - 1];
99+ _progress.IncrementRecords();
100+ _progress.Show(worker, lastDate);
78101 lock (_syncObject)
79102 {
80- _date = date;
81- _brandQueue = new Queue<Brand>(GlobalEnv.BrandData);
103+ _codeQueue = new Queue<string>(codes);
104+ _codeQueue.Dequeue(); // 日経平均を外す。
82105 Monitor.PulseAll(_syncObject);
83106 }
84- _progress.Show(worker, date);
85- var i = 0;
86- for (; i < _progress.RecordsPerDay; i++)
107+ for (var i = 1; i < _progress.RecordsPerDay; i++)
87108 {
88109 if (worker.CancellationPending)
89110 {
@@ -90,7 +111,7 @@
90111 e.Cancel = true;
91112 return;
92113 }
93- Price price;
114+ IEnumerable<Price> prices;
94115 lock (_priceQueue)
95116 {
96117 while (_priceQueue.Count == 0 && _exception == null)
@@ -97,19 +118,18 @@
97118 Monitor.Wait(_priceQueue);
98119 if (_exception != null)
99120 throw _exception;
100- price = _priceQueue.Dequeue();
121+ prices = _priceQueue.Dequeue();
101122 }
102- if (price == null) // 上場廃止
123+ if (prices == null) // 上場廃止
103124 continue;
104- if (price.Code == "1001" && price.Open == 0) // まだ株価が用意されていない。
105- return;
106- PriceData.Add(price, date == end);
125+ foreach (var price in prices)
126+ PriceData.Add(price, false);
107127 _progress.IncrementRecords();
108- _progress.Show(worker, date);
128+ _progress.Show(worker, lastDate);
109129 }
110- PriceData.MaxDate = date;
130+ PriceData.MaxDate = lastDate;
111131 _progress.IncrementDays();
112- }
132+ } while (dates.Count > 0);
113133 }
114134 finally
115135 {
@@ -124,7 +144,7 @@
124144 }
125145 }
126146
127- private void DoFetchPrice()
147+ private void RunFetchPrices()
128148 {
129149 var code = "";
130150 try
@@ -133,15 +153,13 @@
133153 {
134154 lock (_syncObject)
135155 {
136- while ((_brandQueue == null || _brandQueue.Count == 0) && !_terminate)
156+ while ((_codeQueue == null || _codeQueue.Count == 0) && !_terminate)
137157 Monitor.Wait(_syncObject);
138- if (_terminate || _brandQueue == null)
158+ if (_terminate || _codeQueue == null)
139159 return;
140- code = _brandQueue.Dequeue().Code;
160+ code = _codeQueue.Dequeue();
141161 }
142- var price = ParsePage(GetPage(code));
143- if (price != null)
144- price.Code = code;
162+ var price = FetchPrices(code, _series);
145163 lock (_priceQueue)
146164 {
147165 _priceQueue.Enqueue(price);
@@ -153,14 +171,20 @@
153171 {
154172 lock (_priceQueue)
155173 {
156- _exception = new Exception(string.Format("{0}: {1} {2:d}", e.Message, code, _date), e);
174+ _exception = new Exception(string.Format("{0}: {1} {2:d}", e.Message, code, _series[0]), e);
157175 Monitor.Pulse(_priceQueue);
158176 }
159177 }
160178 }
161179
162- private string GetPage(string code)
180+ private IEnumerable<Price> FetchPrices(string code, IList<DateTime> dates)
163181 {
182+ var page = GetPage(code, dates[0], dates[dates.Count - 1]);
183+ return ParsePage(code, page, dates);
184+ }
185+
186+ private string GetPage(string code, DateTime begin, DateTime end)
187+ {
164188 if (code == "1001")
165189 code = "998407";
166190 else if (code == "1002")
@@ -167,7 +191,7 @@
167191 code = "998405";
168192 var dl = new DownloadUtil(string.Format(
169193 "http://info.finance.yahoo.co.jp/history/?code={0}&sy={1}&sm={2}&sd={3}&ey={4}&em={5}&ed={6}&tm=d",
170- code, _date.Year, _date.Month, _date.Day, _date.Year, _date.Month, _date.Day));
194+ code, begin.Year, begin.Month, begin.Day, end.Year, end.Month, end.Day));
171195 for (var i = 0; i < 10; i++)
172196 {
173197 try
@@ -196,13 +220,7 @@
196220 throw new Exception(string.Format("ページの取得に失敗しました。"));
197221 }
198222
199- /// <summary>
200- /// Webページをパースして株価データを取り出す。
201- /// </summary>
202- /// <param name="buf">Webページの文字列</param>
203- /// <returns>上場廃止ならnullを返す。出来高がないか当日の株価がまだ用意されていないなら数字が0のデータを返す。それ以外は取り出した株価データを返す。</returns>
204- /// <exception cref="Exception">Webページのフォーマットが想定と違う場合にスローされる。</exception>
205- private Price ParsePage(string buf)
223+ private IEnumerable<Price> ParsePage(string code, string buf, IEnumerable<DateTime> dates)
206224 {
207225 var valid = new Regex(
208226 @"<td>(?<year>\d{4})年(?<month>1?\d)月(?<day>\d?\d)日</td>" +
@@ -214,23 +232,28 @@
214232
215233 if (buf == null)
216234 return null;
217- var m = valid.Match(buf);
218- if (!m.Success)
235+ var dict = new Dictionary<DateTime, Price>();
236+ var matches = valid.Matches(buf);
237+ if (matches.Count == 0)
219238 {
220239 if (obs.Match(buf).Success || empty.Match(buf).Success) // 上場廃止(銘柄データが空のこともある)
221240 return null;
222- if (invalid.Match(buf).Success) // 出来高がないか株価が用意されていない。
223- return new Price {Date = _date, Open = 0, High = 0, Low = 0, Close = 0, Volume = 0.0};
224- throw new Exception("ページから株価を取得できません。");
241+ if (!invalid.Match(buf).Success)
242+ throw new Exception("ページから株価を取得できません。");
243+ // ここに到達するのは出来高がないか株価が用意されていない場合
225244 }
226245 try
227246 {
228247 const NumberStyles s = NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands;
229- return new Price
248+ foreach (Match m in matches)
249+ {
250+ var date = new DateTime(int.Parse(m.Groups["year"].Value),
251+ int.Parse(m.Groups["month"].Value),
252+ int.Parse(m.Groups["day"].Value));
253+ dict[date] = new Price
230254 {
231- Date = new DateTime(int.Parse(m.Groups["year"].Value),
232- int.Parse(m.Groups["month"].Value),
233- int.Parse(m.Groups["day"].Value)),
255+ Date = date,
256+ Code = code,
234257 Open = (int)double.Parse(m.Groups["open"].Value, s),
235258 High = (int)double.Parse(m.Groups["high"].Value, s),
236259 Low = (int)double.Parse(m.Groups["low"].Value, s),
@@ -237,11 +260,20 @@
237260 Close = (int)double.Parse(m.Groups["close"].Value, s),
238261 Volume = m.Groups["volume"].Value == "" ? 0.0 : double.Parse(m.Groups["volume"].Value, s) / 1000
239262 };
263+ }
240264 }
241265 catch (FormatException e)
242266 {
243267 throw new Exception("ページから株価を取得できません。", e);
244268 }
269+ // 出来高がない日の株価データがないので値が0のデータを補う。
270+ var result = new List<Price>();
271+ foreach (var date in dates)
272+ {
273+ Price price;
274+ result.Add(dict.TryGetValue(date, out price) ? price : new Price { Date = date, Code = code });
275+ }
276+ return result;
245277 }
246278
247279 /// <summary>
Show on old repository browser