/* * Copyright (c) Daisuke OKAJIMA All rights reserved. * * $Id$ * */ using System; using System.Text; using System.IO; using System.Diagnostics; //☆DreamVisor終了対応 新規追加 using System.Collections.Generic; using System.Globalization; using System.Net; using System.Text.RegularExpressions; using System.Threading; //ここまで using Zanetti.Data; namespace Zanetti.DataSource.Specialized { internal class KenMilleDataSource : FullDataSource { public KenMilleDataSource(CodeEnumerator ce) : base(ce) { } public override byte[] DownloadFullData(BasicBrand br) { MemoryStream s = null; try { int code = br.Code; s = Util.HttpDownload(FormatDailyTimeSeriesURL(code, br.Market)); s.Close(); return s.ToArray(); } finally { if(s!=null) s.Close(); } } public override void ImportFullData(BasicBrand br, byte[] buf) { //Debug.WriteLine("DL START"); FileStream d = null; int code = br.Code; string filename = Util.GetDailyDataFileName(code); bool success = false; try { d = new FileStream(filename, FileMode.Create); int records = buf[0] + (buf[1]*256); //最初の2バイトでレコード数を示している if(records > 10000) throw new FormatException("レコード数が異常です"); int offset = 4; bool body_found = false; if(!VolumeIsAvailable(code)) body_found = true; //出来高データがないとわかっている奴は最初からtrue if(code==(int)BuiltInIndex.Nikkei225 || code==(int)BuiltInIndex.TOPIX) body_found = true; //日経平均、TOPIXは過去データに出来高0のゾーンがある for(int i=0; i 21000101) throw new FormatException("日付フォーマットが不正です。"); } } } } //☆DreamVisor終了対応 新規追加 internal class KenMilleDataVariousSource : DailyDataNoDatesSource { private readonly Object _syncObject = new object(); private readonly List _codes = new List(); private Queue _codeQueue; private readonly Queue _resultQueue = new Queue(); private bool _terminate; private Exception _exception; private const int DaysAtOnce = 5; //ダウンロード時間用なのでテキトー protected AbstractBrand _targetBrand; private class FetchResult { public enum Status { Success, Failure, Obsolete, Retry, } public int Code; public SortedDictionary Prices; public Status ReturnStatus; } public KenMilleDataVariousSource(CodeEnumerator ce) : base(ce) { _targetBrand = ce.Next; while (_targetBrand != null) { if (_targetBrand is BasicBrand) { try { _codes.Add(_targetBrand.Code); } catch (Exception ex) { Console.WriteLine("Download failed code " + _targetBrand.Code); Util.SilentReportCriticalError(ex); _errorMessage = ex.Message; SendMessage(AsyncConst.WM_ASYNCPROCESS, _targetBrand.Code, AsyncConst.LPARAM_PROGRESS_FAILURE); } } _targetBrand = ce.Next; } } public override int TotalStep { get { return 3; }//何レコード取るかはユーザーの更新状況に因るので3はテキトー } public override void Run() { var threads = new Thread[1]; for (var i = 0; i < threads.Length; i++) (threads[i] = new Thread(RunFetchPrices) { Name = "Fetch Thread " + i }).Start(); _codeQueue = new Queue(_codes); try { var retry = 0; while (true) { int numCodes; lock (_syncObject) { numCodes = _codeQueue.Count; Monitor.PulseAll(_syncObject); } for (var i = 0; i < numCodes; i++) { FetchResult result; lock (_resultQueue) { while (_resultQueue.Count == 0 && _exception == null) Monitor.Wait(_resultQueue); if (_exception != null) throw _exception; result = _resultQueue.Dequeue(); } switch (result.ReturnStatus) { case FetchResult.Status.Failure: case FetchResult.Status.Obsolete: continue; case FetchResult.Status.Retry: lock (_codeQueue) { _codeQueue.Enqueue(result.Code); } continue; } UpdateDataFarm(result.Code, result.Prices); SendMessage(AsyncConst.WM_ASYNCPROCESS, result.Code, AsyncConst.LPARAM_PROGRESS_SUCCESSFUL); } if (_codeQueue.Count == 0) break; if (retry++ == 10) throw new Exception(string.Format("株価の取得に失敗しました。時間を置いて再試行してください。")); Thread.Sleep(10000); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { lock (_syncObject) { _terminate = true; Monitor.PulseAll(_syncObject); } foreach (var thread in threads) thread.Join(); } } public void UpdateDataFarm(int code, SortedDictionary prices) { var farm = (DailyDataFarm)Env.BrandCollection.FindBrand(code).CreateDailyFarm(prices.Count); var empty = farm.IsEmpty; var skip = true; foreach (var pair in prices) { if (empty && skip && pair.Value.volume == 0) continue; skip = false; farm.UpdateDataFarm(pair.Key, pair.Value); } farm.Save(Util.GetDailyDataFileName(code)); } private void RunFetchPrices() { var code = 0; try { while (true) { lock (_syncObject) { while ((_codeQueue == null || _codeQueue.Count == 0) && !_terminate) Monitor.Wait(_syncObject); if (_terminate || _codeQueue == null) return; code = _codeQueue.Dequeue(); } var result = FetchPrices(code); lock (_resultQueue) { _resultQueue.Enqueue(result); Monitor.Pulse(_resultQueue); } } } catch (Exception e) { lock (_resultQueue) { _exception = new Exception(string.Format("{0}: {1}", e.Message, code), e); Monitor.Pulse(_resultQueue); } } } private FetchResult FetchPrices(int code) { string page; var status = GetPage(code, out page); if (status == FetchResult.Status.Failure || status == FetchResult.Status.Retry) return new FetchResult { Code = code, ReturnStatus = status }; return ParsePage(code, page); } //ケンミレのジャスダックは有料サイトにしかない private string CodeString(int code) { var codestring = string.Empty; switch (code) { case (int)BuiltInIndex.Nikkei225_F: codestring = "0101I"; break; case (int)BuiltInIndex.TOPIX_F: codestring = "0106I"; break; //case (int)BuiltInIndex.Mothers: // codestring = "0130I"; // break; default: codestring = string.Empty; break; } return codestring; } private FetchResult.Status GetPage(int code, out string page) { var url = string.Format( "http://miller.co.jp/kmp00/visitor/apps/cgi-bin/c00cht06.cgi?{0}", CodeString(code)); page = null; try { using (var reader = new StreamReader(Util.HttpDownload(url), System.Text.Encoding.GetEncoding("shift-jis"))) page = reader.ReadToEnd(); } catch (WebException e) { switch (e.Status) { case WebExceptionStatus.ProtocolError: switch (((HttpWebResponse)e.Response).StatusCode) { case (HttpStatusCode)999: case HttpStatusCode.InternalServerError: case HttpStatusCode.BadGateway: return FetchResult.Status.Retry; } throw; case WebExceptionStatus.Timeout: case WebExceptionStatus.ConnectionClosed: case WebExceptionStatus.ReceiveFailure: case WebExceptionStatus.ConnectFailure: return FetchResult.Status.Retry; default: throw; } } return FetchResult.Status.Success; } private FetchResult ParsePage(int code, string buf) { var valid = new Regex( @"(?\d{4})/(?\d?\d)/(?\d?\d)+" + "(?[0-9,.]+)" + "(?[0-9,.]+)" + "(?[0-9,.]+)" + "(?[0-9,.]+)" + "([-+.,0-9]+|0)" + "(?[0-9,.]+)" ); var invalid = new Regex("該当する期間のデータはありません。
期間をご確認ください。"); var obs = new Regex("該当する銘柄はありません。
再度銘柄(コード)を入力し、「表示」ボタンを押してください。"); var empty = new Regex("
\n
"); if (buf == null) return null; var dict = new SortedDictionary(); var matches = valid.Matches(buf.Replace("\r\n", "").Replace("\n", "").Replace("\t", "").Replace("bg-high", "").Replace("bg-low", "").Replace("(", "").Replace(")", "").Replace("月", "").Replace("火", "").Replace("水", "").Replace("木", "").Replace("金", "").Replace("土", "").Replace("日", "")); if (matches.Count == 0)//存在しなかったらスルーするだけ { if (obs.Match(buf).Success || empty.Match(buf).Success) // 上場廃止(銘柄データが空のこともある) return new FetchResult { ReturnStatus = FetchResult.Status.Obsolete }; //else if (!invalid.Match(buf).Success) // throw new Exception("ページから株価を取得できません。"); else return new FetchResult { Code = code, Prices = dict, ReturnStatus = FetchResult.Status.Success }; } try { var shift = code == 151 ? 1 : IsIndex(code) ? 100 : 10; // 指数は100倍、株式は10倍で記録する const NumberStyles s = NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands; foreach (Match m in matches) { var date = new DateTime(int.Parse(m.Groups["year"].Value), int.Parse(m.Groups["month"].Value), int.Parse(m.Groups["day"].Value)); dict[Util.DateToInt(date)] = new NewDailyData { open = (int)(double.Parse(m.Groups["open"].Value, s) * shift), high = (int)(double.Parse(m.Groups["high"].Value, s) * shift), low = (int)(double.Parse(m.Groups["low"].Value, s) * shift), close = (int)(double.Parse(m.Groups["close"].Value, s) * shift), volume = (int)(double.Parse(m.Groups["volume"].Value.Replace(".", ""), s)) }; } } catch (FormatException e) { throw new Exception("ページから株価を取得できません。", e); } return new FetchResult { Code = code, Prices = dict, ReturnStatus = FetchResult.Status.Success }; } private bool IsIndex(int code) { return code == (int)BuiltInIndex.Nikkei225 || code == (int)BuiltInIndex.TOPIX; } } }