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