2021-01-10 darai <darai@users.sourceforge.jp>
#41025: バイナリデータ読み込み速度向上案
* Protra.Lib/Data/PriceData.cs (PriceData.GetPrices): バイナリデータ読み込み速度向上案反映。
@@ -1,3 +1,9 @@ | ||
1 | +2021-01-10 darai <darai@users.sourceforge.jp> | |
2 | + | |
3 | + #41025: バイナリデータ読み込み速度向上案 | |
4 | + | |
5 | + * Protra.Lib/Data/PriceData.cs (PriceData.GetPrices): バイナリデータ読み込み速度向上案反映。 | |
6 | + | |
1 | 7 | 2020-10-03 darai <darai@users.sourceforge.jp> |
2 | 8 | |
3 | 9 | #40826: 東証障害による株価データダウンロードエラー |
@@ -1,4 +1,4 @@ | ||
1 | -// Copyright(C) 2010, 2013, 2014, 2018 panacoran <panacoran@users.sourceforge.jp> | |
1 | +// Copyright(C) 2010, 2013, 2014 panacoran <panacoran@users.sourceforge.jp> | |
2 | 2 | // |
3 | 3 | // This program is part of Protra. |
4 | 4 | // |
@@ -35,7 +35,7 @@ | ||
35 | 35 | /// <summary> |
36 | 36 | /// レコードサイズ |
37 | 37 | /// </summary> |
38 | - public static readonly int RecordSize = 4 /* date */ + 4 * 4 /* prices */ + 8 /* volume */; | |
38 | + public static readonly int RecordSize = 4 /* date */+ 4 * 4 /* prices */+ 8 /* volume */; | |
39 | 39 | |
40 | 40 | /// <summary> |
41 | 41 | /// 証券コード |
@@ -144,22 +144,31 @@ | ||
144 | 144 | /// <summary> |
145 | 145 | /// タイムフレームを取得する。 |
146 | 146 | /// </summary> |
147 | - public TimeFrame TimeFrame { get; } | |
147 | + public TimeFrame TimeFrame { get; private set; } | |
148 | 148 | |
149 | 149 | /// <summary> |
150 | 150 | /// 最初の株価データを取得する。 |
151 | 151 | /// </summary> |
152 | - public Price First => _priceList.Count > 0 ? _priceList[0] : null; | |
152 | + public Price First | |
153 | + { | |
154 | + get { return _priceList.Count > 0 ? _priceList[0] : null; } | |
155 | + } | |
153 | 156 | |
154 | 157 | /// <summary> |
155 | 158 | /// 最後の株価データを取得する。 |
156 | 159 | /// </summary> |
157 | - public Price Last => _priceList.Count > 0 ? _priceList[_priceList.Count - 1] : null; | |
160 | + public Price Last | |
161 | + { | |
162 | + get { return _priceList.Count > 0 ? _priceList[_priceList.Count - 1] : null; } | |
163 | + } | |
158 | 164 | |
159 | 165 | /// <summary> |
160 | 166 | /// リストの要素数を返す。 |
161 | 167 | /// </summary> |
162 | - public int Count => _priceList.Count; | |
168 | + public int Count | |
169 | + { | |
170 | + get { return _priceList.Count; } | |
171 | + } | |
163 | 172 | |
164 | 173 | /// <summary> |
165 | 174 | /// コンストラクタ |
@@ -195,7 +204,10 @@ | ||
195 | 204 | /// </summary> |
196 | 205 | /// <param name="index"></param> |
197 | 206 | /// <returns></returns> |
198 | - public Price this[int index] => _priceList[index]; | |
207 | + public Price this[int index] | |
208 | + { | |
209 | + get { return _priceList[index]; } | |
210 | + } | |
199 | 211 | |
200 | 212 | /// <summary> |
201 | 213 | /// 指定した日付を持つ株価データのインデックスを返す。 |
@@ -258,7 +270,6 @@ | ||
258 | 270 | var dir = Path.Combine(Global.DirPrice, code.Substring(0, 1)); |
259 | 271 | if (!Directory.Exists(dir)) |
260 | 272 | Directory.CreateDirectory(dir); |
261 | - | |
262 | 273 | return Path.Combine(dir, code); |
263 | 274 | } |
264 | 275 |
@@ -268,23 +279,20 @@ | ||
268 | 279 | /// <param name="code">証券コード</param> |
269 | 280 | /// <param name="timeFrame">タイムフレーム</param> |
270 | 281 | /// <param name="needLastWeek">終わっていない週足を返すか</param> |
271 | - /// <param name="applySplit">分割を適用するか</param> | |
272 | - public static PriceList GetPrices(string code, TimeFrame timeFrame, bool needLastWeek = false, | |
273 | - bool applySplit = true) | |
282 | + public static PriceList GetPrices(string code, TimeFrame timeFrame, bool needLastWeek = false) | |
274 | 283 | { |
275 | 284 | var file = PricePath(code); |
276 | 285 | if (!File.Exists(file)) |
277 | 286 | return null; |
278 | - | |
279 | 287 | var prices = new List<Price>(); |
280 | 288 | using (var s = new FileStream(file, FileMode.Open)) |
281 | - { | |
282 | 289 | try |
283 | 290 | { |
284 | 291 | var buf = new byte[s.Length]; |
285 | 292 | s.Read(buf, 0, (int)s.Length); |
286 | 293 | var b = new BinaryReader(new MemoryStream(buf)); |
287 | - while (true) | |
294 | + var baseStream = b.BaseStream; | |
295 | + while (baseStream.Position != baseStream.Length) | |
288 | 296 | { |
289 | 297 | var p = new Price {Code = code}; |
290 | 298 | p.Read(b); |
@@ -294,25 +302,16 @@ | ||
294 | 302 | catch (EndOfStreamException) |
295 | 303 | { |
296 | 304 | } |
297 | - } | |
298 | - | |
299 | - if (applySplit) | |
305 | + foreach (var split in GlobalEnv.BrandData[code].Split) | |
300 | 306 | { |
301 | - foreach (var split in GlobalEnv.BrandData[code].Split) | |
302 | - { | |
303 | - if (split.Date > prices[prices.Count - 1].Date) | |
304 | - continue; | |
305 | - | |
306 | - foreach (var price in prices) | |
307 | - { | |
308 | - if (price.Date < split.Date) | |
309 | - price.Split(split.Ratio); | |
310 | - else | |
311 | - break; | |
312 | - } | |
313 | - } | |
307 | + if (split.Date > prices[prices.Count - 1].Date) | |
308 | + continue; | |
309 | + foreach (var price in prices) | |
310 | + if (price.Date < split.Date) | |
311 | + price.Split(split.Ratio); | |
312 | + else | |
313 | + break; | |
314 | 314 | } |
315 | - | |
316 | 315 | return timeFrame == TimeFrame.Daily |
317 | 316 | ? new PriceList(prices, TimeFrame.Daily) |
318 | 317 | : GenerateWeeklyPrices(prices, needLastWeek); |
@@ -322,7 +321,6 @@ | ||
322 | 321 | { |
323 | 322 | if (daily.Count == 0) // 空のデータファイルが存在する場合。 |
324 | 323 | return new PriceList(daily, TimeFrame.Weekly); |
325 | - | |
326 | 324 | var weekly = new List<Price>(); |
327 | 325 | var code = daily[0].Code; |
328 | 326 | var date = daily[0].Date; |
@@ -359,19 +357,14 @@ | ||
359 | 357 | { |
360 | 358 | if (d.High > high) |
361 | 359 | high = d.High; |
362 | - | |
363 | - if (low == 0 || d.Low > 0 && d.Low < low) | |
360 | + if (low == 0 || (d.Low > 0 && d.Low < low)) | |
364 | 361 | low = d.Low; |
365 | - | |
366 | 362 | if (open == 0) // 最初に付いた値段が始値 |
367 | 363 | open = d.Open; |
368 | - | |
369 | 364 | if (d.Close != 0) |
370 | 365 | close = d.Close; |
371 | - | |
372 | 366 | volume += d.Volume; |
373 | 367 | } |
374 | - | |
375 | 368 | prevDow = d.Date.DayOfWeek; |
376 | 369 | } |
377 | 370 |
@@ -390,7 +383,6 @@ | ||
390 | 383 | }; |
391 | 384 | weekly.Add(w); |
392 | 385 | } |
393 | - | |
394 | 386 | return new PriceList(weekly, TimeFrame.Weekly); |
395 | 387 | } |
396 | 388 |
@@ -403,13 +395,13 @@ | ||
403 | 395 | /// <param name="close">ファイルを閉じるか</param> |
404 | 396 | public static void Add(Price price, bool close) |
405 | 397 | { |
406 | - if (!OpenFiles.TryGetValue(price.Code, out var s)) | |
398 | + FileStream s; | |
399 | + if (!OpenFiles.TryGetValue(price.Code, out s)) | |
407 | 400 | { |
408 | 401 | var file = PricePath(price.Code); |
409 | 402 | s = new FileStream(file, FileMode.OpenOrCreate); |
410 | 403 | OpenFiles.Add(price.Code, s); |
411 | 404 | } |
412 | - | |
413 | 405 | if (s.Length >= Price.RecordSize) |
414 | 406 | { |
415 | 407 | s.Seek(-1 * Price.RecordSize, SeekOrigin.End); |
@@ -418,12 +410,10 @@ | ||
418 | 410 | if (price.Date <= last.Date) // すでに存在する。 |
419 | 411 | goto Exit; |
420 | 412 | } |
421 | - | |
422 | 413 | price.Write(new BinaryWriter(s)); |
423 | 414 | Exit: |
424 | 415 | if (!close) |
425 | 416 | return; |
426 | - | |
427 | 417 | s.Close(); |
428 | 418 | OpenFiles.Remove(price.Code); |
429 | 419 | } |
@@ -435,7 +425,6 @@ | ||
435 | 425 | { |
436 | 426 | foreach (var s in OpenFiles.Values) |
437 | 427 | s.Close(); |
438 | - | |
439 | 428 | OpenFiles.Clear(); |
440 | 429 | } |
441 | 430 |
@@ -483,10 +472,8 @@ | ||
483 | 472 | r.Read(b); |
484 | 473 | if (r.Date < since) |
485 | 474 | break; |
486 | - | |
487 | 475 | s.Seek(-2 * Price.RecordSize, SeekOrigin.Current); |
488 | 476 | } |
489 | - | |
490 | 477 | s.SetLength(s.Seek(0, SeekOrigin.Current)); |
491 | 478 | } |
492 | 479 | catch (IOException) |
@@ -497,7 +484,6 @@ | ||
497 | 484 | s.Close(); |
498 | 485 | } |
499 | 486 | } |
500 | - | |
501 | 487 | MaxDate = since.AddDays(-1); |
502 | 488 | } |
503 | 489 |
@@ -513,14 +499,12 @@ | ||
513 | 499 | foreach (var file in CollectFiles("")) |
514 | 500 | { |
515 | 501 | var code = Path.GetFileName(file); |
516 | - if (string.Compare(code, start, StringComparison.Ordinal) < 0 || | |
517 | - string.Compare(code, end, StringComparison.Ordinal) > 0) | |
502 | + if (String.Compare(code, start, StringComparison.Ordinal) < 0 || | |
503 | + String.Compare(code, end, StringComparison.Ordinal) > 0) | |
518 | 504 | continue; |
519 | - | |
520 | - var prices = GetPrices(code, TimeFrame.Daily, applySplit: false); | |
505 | + var prices = GetPrices(code, TimeFrame.Daily); | |
521 | 506 | if (prices == null) |
522 | 507 | continue; |
523 | - | |
524 | 508 | if (!overwriteAll && File.Exists(file + ".csv")) |
525 | 509 | { |
526 | 510 | using (var dialog = new OverwriteDialog()) |
@@ -530,15 +514,12 @@ | ||
530 | 514 | var result = dialog.ShowDialog(); |
531 | 515 | if (result == DialogResult.Cancel) |
532 | 516 | return false; |
533 | - | |
534 | 517 | if (result == DialogResult.No) |
535 | 518 | continue; |
536 | - | |
537 | 519 | if (result == DialogResult.OK) |
538 | 520 | overwriteAll = true; |
539 | 521 | } |
540 | 522 | } |
541 | - | |
542 | 523 | FileStream f; |
543 | 524 | Retry: |
544 | 525 | try |
@@ -550,26 +531,22 @@ | ||
550 | 531 | DialogResult result; |
551 | 532 | using (new CenteredDialogHelper()) |
552 | 533 | result = MessageBox.Show(e.Message, "株価データの変換", MessageBoxButtons.AbortRetryIgnore); |
553 | - | |
554 | 534 | if (result == DialogResult.Abort) |
555 | 535 | return false; |
556 | - | |
557 | 536 | if (result == DialogResult.Ignore) |
558 | 537 | continue; |
559 | - | |
560 | 538 | goto Retry; |
561 | 539 | } |
562 | - | |
563 | 540 | using (var txt = new StreamWriter(f, Encoding.ASCII)) |
564 | 541 | { |
565 | 542 | foreach (var p in prices) |
566 | - { | |
567 | - txt.WriteLine(string.Join(",", p.Date.ToString("d"), p.Open.ToString(), p.High.ToString(), | |
568 | - p.Low.ToString(), p.Close.ToString(), p.Volume.ToString())); | |
569 | - } | |
543 | + txt.WriteLine(string.Join(",", new[] | |
544 | + { | |
545 | + p.Date.ToString("d"), p.Open.ToString(), | |
546 | + p.High.ToString(), p.Low.ToString(), p.Close.ToString(), p.Volume.ToString() | |
547 | + })); | |
570 | 548 | } |
571 | 549 | } |
572 | - | |
573 | 550 | return true; |
574 | 551 | } |
575 | 552 |
@@ -585,10 +562,9 @@ | ||
585 | 562 | foreach (var file in CollectFiles(".csv")) |
586 | 563 | { |
587 | 564 | var code = Path.GetFileNameWithoutExtension(file); |
588 | - if (string.Compare(code, start, StringComparison.Ordinal) < 0 || | |
589 | - string.Compare(code, end, StringComparison.Ordinal) > 0) | |
565 | + if (String.Compare(code, start, StringComparison.Ordinal) < 0 || | |
566 | + String.Compare(code, end, StringComparison.Ordinal) > 0) | |
590 | 567 | continue; |
591 | - | |
592 | 568 | var prices = new List<Price>(); |
593 | 569 | Retry: |
594 | 570 | var num = 0; |
@@ -619,14 +595,10 @@ | ||
619 | 595 | { |
620 | 596 | DialogResult result; |
621 | 597 | using (new CenteredDialogHelper()) |
622 | - { | |
623 | 598 | result = MessageBox.Show(file + "の" + num + "行目に不正な値があります。" + |
624 | 599 | "このファイルを無視して処理を継続しますか?", "株価データの変換", MessageBoxButtons.YesNo); |
625 | - } | |
626 | - | |
627 | 600 | if (result == DialogResult.No) |
628 | 601 | return false; |
629 | - | |
630 | 602 | continue; |
631 | 603 | } |
632 | 604 | catch (IOException e) |
@@ -634,16 +606,12 @@ | ||
634 | 606 | DialogResult result; |
635 | 607 | using (new CenteredDialogHelper()) |
636 | 608 | result = MessageBox.Show(e.Message, "株価データの変換", MessageBoxButtons.AbortRetryIgnore); |
637 | - | |
638 | 609 | if (result == DialogResult.Abort) |
639 | 610 | return false; |
640 | - | |
641 | 611 | if (result == DialogResult.Ignore) |
642 | 612 | continue; |
643 | - | |
644 | 613 | goto Retry; |
645 | 614 | } |
646 | - | |
647 | 615 | var dst = file.Substring(0, file.Length - 4); |
648 | 616 | if (!overwriteAll && File.Exists(dst)) |
649 | 617 | { |
@@ -654,25 +622,20 @@ | ||
654 | 622 | var result = dialog.ShowDialog(); |
655 | 623 | if (result == DialogResult.Cancel) |
656 | 624 | return false; |
657 | - | |
658 | 625 | if (result == DialogResult.No) |
659 | 626 | continue; |
660 | - | |
661 | 627 | if (result == DialogResult.OK) |
662 | 628 | overwriteAll = true; |
663 | 629 | } |
664 | 630 | } |
665 | - | |
666 | 631 | File.Delete(dst); |
667 | 632 | foreach (var p in prices) |
668 | 633 | Add(p, false); |
669 | - | |
670 | 634 | CloseAll(); |
671 | 635 | var max = MaxDateByCode(code); |
672 | 636 | if (max > MaxDate) |
673 | 637 | MaxDate = max; |
674 | 638 | } |
675 | - | |
676 | 639 | return true; |
677 | 640 | } |
678 | 641 |
@@ -679,19 +642,18 @@ | ||
679 | 642 | /// <summary> |
680 | 643 | /// 指定した拡張子のファイルを株価データのディレクトリから探す。 |
681 | 644 | /// </summary> |
682 | - /// <param name="extenstion">拡張子を指定する。</param> | |
645 | + /// <param name="extention">拡張子を指定する。</param> | |
683 | 646 | /// <returns>ファイルのリストを返す。</returns> |
684 | - private static IEnumerable<string> CollectFiles(string extenstion) | |
647 | + private static IEnumerable<string> CollectFiles(string extention) | |
685 | 648 | { |
686 | 649 | var result = new List<string>(); |
687 | 650 | foreach (var dir in Directory.GetDirectories(Global.DirPrice, "*")) |
688 | - foreach (var path in Directory.GetFiles(Path.Combine(Global.DirPrice, dir), "*")) | |
689 | - { | |
690 | - var ext = Path.GetExtension(path); | |
691 | - if (string.Compare(ext, extenstion, StringComparison.InvariantCultureIgnoreCase) == 0) | |
692 | - result.Add(path); | |
693 | - } | |
694 | - | |
651 | + foreach (var path in Directory.GetFiles(Path.Combine(Global.DirPrice, dir), "*")) | |
652 | + { | |
653 | + var ext = Path.GetExtension(path); | |
654 | + if (ext != null && String.Compare(ext, extention, StringComparison.InvariantCultureIgnoreCase) == 0) | |
655 | + result.Add(path); | |
656 | + } | |
695 | 657 | return result; |
696 | 658 | } |
697 | 659 | } |