/* * Copyright (c) Daisuke OKAJIMA All rights reserved. * * $Id$ */ // Copyright (c) 2014 panacoran <panacoran@users.sourceforge.jp> // This program is part of OmegaChart. // OmegaChart is licensed under the Apache License, Version 2.0. using System; using System.IO; using System.Collections; using System.Diagnostics; using System.Text; using Zanetti.Arithmetic; using Zanetti.Arithmetic.Series; using Zanetti.Indicators; namespace Zanetti.Data { //primitive indicatorをサポートするためのもの internal class DataFarmPrimitiveAccess { //delegateの引数になるためにこの形でないとだめ internal static double GetDate(TradeData tr) { return (double)tr.Farm.GetInt(tr.Offset); } internal static double GetOpen(TradeData tr) { return AdjustPrice((double)tr.Farm.GetInt(tr.Offset + DataFarm.OPEN_OFFSET), tr); } internal static double GetHigh(TradeData tr) { return AdjustPrice((double)tr.Farm.GetInt(tr.Offset + DataFarm.HIGH_OFFSET), tr); } internal static double GetLow(TradeData tr) { return AdjustPrice((double)tr.Farm.GetInt(tr.Offset + DataFarm.LOW_OFFSET), tr); } internal static double GetClose(TradeData tr) { return AdjustPrice((double)tr.Farm.GetInt(tr.Offset + DataFarm.CLOSE_OFFSET), tr); } internal static double GetVolume(TradeData tr) { //オーバーフロー対策の一時しのぎ return AdjustVolume((double)(uint)tr.Farm.GetInt(tr.Offset + DataFarm.VOLUME_OFFSET), tr); } internal static double GetCreditLong(TradeData tr) { return AdjustVolume((double)tr.Farm.GetInt(tr.Offset + DataFarm.CREDITLONG_OFFSET), tr); } internal static double GetCreditShort(TradeData tr) { return AdjustVolume((double)tr.Farm.GetInt(tr.Offset + DataFarm.CREDITSHORT_OFFSET), tr); } private static double AdjustPrice(double value, TradeData tr) { double split = Env.Preference.AdjustSplit ? tr.Farm.CalcSplitRatio(tr.Date) : 1; if (value == 0 && GetVolume(tr) == 0) { //出来高がない日は価格が0と記入されているので前日の終値で代用 TradeData pr = tr.Prev; return pr == null ? 0 : GetClose(tr.Prev); } else return tr.Farm.Brand.PriceScale * value / split; } private static double AdjustVolume(double value, TradeData tr) { double split = Env.Preference.AdjustSplit ? tr.Farm.CalcSplitRatio(tr.Date) : 1; return value * split; } } internal class NewDailyData { public int open; public int high; public int low; public int close; public int volume; } internal abstract class DataFarm : IDisposable { public const int RECORD_LENGTH = 32; public const int OPEN_OFFSET = 4; public const int HIGH_OFFSET = 8; public const int LOW_OFFSET = 12; public const int CLOSE_OFFSET = 16; public const int VOLUME_OFFSET = 20; public const int CREDITSHORT_OFFSET = 24; public const int CREDITLONG_OFFSET = 28; protected bool _isEmpty; //エラーなどで利用不能なことを示すフラグ protected AbstractBrand _brand; protected byte[] _farm; //一次データ。同一のDataFarmオブジェクトを他の銘柄に使いまわすときもあるので、必要以上の長さが確保されることもある protected int _byteLength; //_farmの論理的な長さ protected TradeData[] _data; //必要に応じて生成されるTradeDataの列。一目など未来の日付のデータがあると配列の長さは_farmに対応する分より大きいこともある protected int _filledLength; //最新日付までの長さ public DataFarm() { } public abstract void LoadFor(AbstractBrand br); public AbstractBrand Brand { get { return _brand; } } public int TotalLength { get { return _data.Length; } } public int FilledLength { get { return _filledLength; } } public bool IsEmpty { get { return _isEmpty; } } public byte[] RawDataImage { get { return _farm; } } internal int GetInt(int offset) { if (offset >= _byteLength) throw new IndexOutOfRangeException(); unsafe { fixed (byte* p = &_farm[0]) { return *(int*)(p + offset); } } } public TradeData GetByIndex(int index) { Debug.Assert(_data != null); if (index < 0 || index >= _data.Length) throw new TradeDataOverflowException(index.ToString() + " is out of range"); TradeData td = _data[index]; if (td != null) return td; //cache hit td = new TradeData(this, index, index * RECORD_LENGTH); _data[index] = td; return td; } public abstract int LastDate { get; } public abstract int FirstDate { get; } public int DateToIndex(int date) { return DateToIndex(0, _filledLength, date); } private int DateToIndex(int begin, int end, int date) { //binary search if (end - begin <= 1) return begin; else { int h = (begin + end) / 2; int t = GetByIndex(h).Date; if (date < t) return DateToIndex(begin, h, date); else return DateToIndex(h, end, date); } } //分割比率の取得 public double CalcSplitRatio(int date) { return _brand.CalcSplitRatio(date, this.LastDate); } public void Dispose() { _farm = null; _data = null; } internal static int GetInt(byte[] rawdata, int offset) { Debug.Assert(rawdata.Length > 0); unsafe { fixed (byte* p = &rawdata[0]) { return *(int*)(p + offset); } } } internal static void SetInt(byte[] rawdata, int offset, int value) { unsafe { fixed (byte* p = &rawdata[0]) { *(int*)(p + offset) = value; } } } internal static void SetUInt(byte[] rawdata, int offset, uint value) { unsafe { fixed (byte* p = &rawdata[0]) { *(uint*)(p + offset) = value; } } } protected static int AdjustPrice(int raw, double ratio) { return (int)((double)raw / ratio); } protected static int AdjustVolume(int raw, double ratio) { return (int)((double)raw * ratio); } } internal class DailyDataFarm : DataFarm { protected int _extraDataOffset; //1日単位でデータの追加をしたときのために public DailyDataFarm() : base() { } public override void LoadFor(AbstractBrand br) { _brand = br; Construct(Util.GetDailyDataFileName(br.Code), 0); } public void LoadFor(AbstractBrand br, int extra_dates) { _brand = br; Construct(Util.GetDailyDataFileName(br.Code), extra_dates); } private void Construct(string filename, int extra_dates) { _isEmpty = true; #if DOJIMA Dojima.DojimaUtil.HalfDailyDataFarmCache.Clear(_brand); #endif if (File.Exists(filename)) { int length = (int)new FileInfo(filename).Length; if (length == 0) return; if (_farm == null || _farm.Length < length + extra_dates * RECORD_LENGTH) _farm = new byte[length + extra_dates * RECORD_LENGTH]; int future_length = Env.CurrentIndicators.GetAddedFutureLength(ChartFormat.Daily); _filledLength = length / RECORD_LENGTH; _data = new TradeData[_filledLength + future_length]; _byteLength = length; _extraDataOffset = 0; _isEmpty = false; FileStream s = null; try { s = new FileStream(filename, FileMode.Open); s.Read(_farm, 0, length); } finally { if (s != null) s.Close(); } // 個別銘柄の株価データの先頭にある出来高0のデータを取り除く var basic = _brand as BasicBrand; if (basic == null || basic.Market == MarketType.B || basic.Market == MarketType.Custom) return; var idx = 0; for (var i = 0; i < _filledLength; i++) { unsafe { var head = i * RECORD_LENGTH; fixed (byte* p = &_farm[0]) { if (*(int*)(p + head + VOLUME_OFFSET) == 0) idx += RECORD_LENGTH; else break; } } } if (idx == 0) return; _byteLength -= idx; _filledLength = _byteLength / RECORD_LENGTH; for (var i = 0; i < _byteLength; i++) _farm[i] = _farm[i + idx]; } } public void Save(string filename) { if (_farm != null) { //エラーハンドリングできていない FileStream s = new FileStream(filename, FileMode.Create); s.Write(_farm, 0, _byteLength + _extraDataOffset); s.Close(); } } internal void WriteExtraData(int record_offset, int value) { unsafe { fixed (byte* p = &_farm[0]) { *(int*)(p + _byteLength + _extraDataOffset + record_offset) = value; } } } internal void ProgressExtraDataAddress() { _extraDataOffset += RECORD_LENGTH; Debug.Assert(_extraDataOffset <= _farm.Length); } //連続的に複数の日付を更新することもできるが、増加方向であることが必須 internal void UpdateDataFarm(int date, NewDailyData td) { int ld; if (this.IsEmpty) { //とりあえず1日書き込める分だけ初期化 if (_farm == null) { _farm = new byte[RECORD_LENGTH * 200]; //この上限はどこかで取得すべきだが _filledLength = 0; _data = null; _byteLength = 0; _extraDataOffset = 0; } ld = 0; } else ld = this.LastDate; int offset; if (ld < date) { offset = _byteLength + _extraDataOffset; //emptyのときは常にこれ _extraDataOffset += RECORD_LENGTH; } else { offset = _byteLength - RECORD_LENGTH; do { int t = GetInt(offset); if (t == date) break; else if (t < date) { offset += RECORD_LENGTH; break; } else offset -= RECORD_LENGTH; } while (true); } unsafe { fixed (byte* p = &_farm[0]) { byte* a = p + offset; *(int*)(a + 0) = date; *(int*)(a + 4) = td.open; *(int*)(a + 8) = td.high; *(int*)(a + 12) = td.low; *(int*)(a + 16) = td.close; *(int*)(a + 20) = td.volume; } } } //次の2つはTradeDataを作らないようにしている、注意 public override int LastDate { get { return GetInt(_byteLength - RECORD_LENGTH); } } public override int FirstDate { get { return GetInt(0); } } } internal class WeeklyDataFarm : DataFarm { private int _firstDate; private int _lastDate; public WeeklyDataFarm() : base() { } public override void LoadFor(AbstractBrand br) { _brand = br; Construct(Util.GetDailyDataFileName(br.Code)); } private void Construct(string filename) { _isEmpty = true; if (File.Exists(filename)) { int length = (int)new FileInfo(filename).Length; if (length > 0) { //まずは日足を読む byte[] daily = new byte[length]; FileStream s = null; try { s = new FileStream(filename, FileMode.Open); s.Read(daily, 0, length); } finally { if (s != null) s.Close(); } _isEmpty = false; _firstDate = GetInt(daily, 0); _lastDate = GetInt(daily, daily.Length - RECORD_LENGTH); var daily_begin = Util.IntToDate(GetInt(daily, 0)); var weekly_begin = daily_begin.AddDays(-(int)daily_begin.DayOfWeek); var daily_end = Util.IntToDate(GetInt(daily, daily.Length - RECORD_LENGTH)); var weekly_end = daily_end.AddDays(-(int)daily_end.DayOfWeek); _filledLength = (int)(weekly_end - weekly_begin).TotalDays / 7 + 1; _data = new TradeData[_filledLength + Env.CurrentIndicators.GetAddedFutureLength(ChartFormat.Weekly)]; //byte[]部分のデータ読み _farm = new byte[_data.Length * RECORD_LENGTH]; _byteLength = _farm.Length; int offset = 0; var weekly = weekly_begin; for (int i = 0; i < _filledLength; i++, weekly = weekly.AddDays(7)) { offset = FillWeeklyData(i * RECORD_LENGTH, daily, offset, Util.DateToInt(weekly)); if (offset >= daily.Length) break; } } } } private int FillWeeklyData(int farmoffset, byte[] daily, int offset, int firstdate) { int enddate = Util.DateToInt(Util.IntToDate(firstdate).AddDays(7)); int vol = 0, high = Int32.MinValue, low = Int32.MaxValue; int open = 0, close = 0, cre_long = 0, cre_short = 0; int today = GetInt(daily, offset); bool is_index = _brand.IsBuiltIn; // base_splitを得るのに最初の取引日である 'today' を使うのは誤り。 // 下の、SetInt(_farm, farmoffset, wi.FirstDate); // で、後に式を評価する際に用いられる基準日として日曜基準で 'wi.FirstDate' を使っているのだから、 // ここでもこの値を使うべき。 2005/3/15 T. SARUKI // double base_split = this.CalcSplitRatio(firstdate); //分割を考慮する場合は期間内の調整が要る while (offset <= daily.Length - RECORD_LENGTH && today < enddate) { //if(!is_index && today>20031201) Debugger.Break(); double split = Env.Preference.AdjustSplit ? this.CalcSplitRatio(today) / base_split : 1; int v = AdjustVolume(GetInt(daily, offset + VOLUME_OFFSET), split); if (is_index || v != 0) { //非indexで出来高0の日は集計しない if (open == 0) open = AdjustPrice(GetInt(daily, offset + OPEN_OFFSET), split); close = AdjustPrice(GetInt(daily, offset + CLOSE_OFFSET), split); high = Math.Max(high, AdjustPrice(GetInt(daily, offset + HIGH_OFFSET), split)); low = Math.Min(low, AdjustPrice(GetInt(daily, offset + LOW_OFFSET), split)); cre_long = AdjustVolume(GetInt(daily, offset + CREDITLONG_OFFSET), split); cre_short = AdjustVolume(GetInt(daily, offset + CREDITSHORT_OFFSET), split); vol += v; } offset += RECORD_LENGTH; if (offset < daily.Length) today = GetInt(daily, offset); } SetInt(_farm, farmoffset, firstdate); SetInt(_farm, farmoffset + OPEN_OFFSET, open); SetInt(_farm, farmoffset + HIGH_OFFSET, high); SetInt(_farm, farmoffset + LOW_OFFSET, low); SetInt(_farm, farmoffset + CLOSE_OFFSET, close); SetInt(_farm, farmoffset + VOLUME_OFFSET, vol); SetInt(_farm, farmoffset + CREDITLONG_OFFSET, cre_long); SetInt(_farm, farmoffset + CREDITSHORT_OFFSET, cre_short); return offset; } public override int LastDate { get { return _lastDate; } } public override int FirstDate { get { return _firstDate; } } } internal class MonthlyDataFarm : DataFarm { private int _firstDate; private int _lastDate; public MonthlyDataFarm() : base() { } public override void LoadFor(AbstractBrand br) { _brand = br; Construct(Util.GetDailyDataFileName(br.Code)); } private void Construct(string filename) { _isEmpty = true; if (File.Exists(filename)) { int length = (int)new FileInfo(filename).Length; if (length > 0) { //まずは日足を読む byte[] daily = new byte[length]; _isEmpty = false; FileStream s = null; try { s = new FileStream(filename, FileMode.Open); s.Read(daily, 0, length); } finally { if (s != null) s.Close(); } _firstDate = GetInt(daily, 0); _lastDate = GetInt(daily, daily.Length - RECORD_LENGTH); DateTime monthly_begin = new DateTime(_firstDate / 10000, (_firstDate % 10000) / 100, (_firstDate % 100)); DateTime monthly_end = new DateTime(_lastDate / 10000, (_lastDate % 10000) / 100, (_lastDate % 100)); _filledLength = (monthly_end.Year - monthly_begin.Year) * 12 + monthly_end.Month + 1 - monthly_begin.Month; _data = new TradeData[_filledLength + Env.CurrentIndicators.GetAddedFutureLength(ChartFormat.Monthly)]; // 以下WeeklyIndexとかぶって冗長 //byte[]部分のデータ読み _farm = new byte[_data.Length * RECORD_LENGTH]; _byteLength = _farm.Length; DateTime yearmonth = monthly_begin; int offset = 0; for (int i = 0; i < _filledLength; i++) { offset = FillMonthlyData(i * RECORD_LENGTH, daily, offset, yearmonth); if (offset >= daily.Length) break; yearmonth = yearmonth.AddMonths(1); } } } } // このメソッドもWeeklyIndexのFillWeeklyDataとかぶってかなり冗長 private int FillMonthlyData(int farmoffset, byte[] daily, int offset, DateTime yearmonth) { DateTime endmonth = yearmonth.AddMonths(1); int enddate = endmonth.Year * 10000 + endmonth.Month * 100 + 1; int vol = 0, high = Int32.MinValue, low = Int32.MaxValue; int open = 0, close = 0, cre_long = 0, cre_short = 0; int today = GetInt(daily, offset); bool is_index = _brand.IsBuiltIn; // base_splitを得るのに最初の取引日である 'today' を使うのは誤り。 // 下の、SetInt(_farm, farmoffset, yearmonth.Year * 10000 + yearmonth.Month * 100 + 1); // で、後に式を評価する際に用いられる基準日として月の初日である 'yearmonth.Year * 10000 + yearmonth.Month * 100 + 1' を使っているのだから、 // ここでもこの値を使うべき。 2005/3/15 T. SARUKI // double base_split = this.CalcSplitRatio(Util.DateToInt(yearmonth.Year, yearmonth.Month, 1)); while (offset <= daily.Length - RECORD_LENGTH && today < enddate) { double split = Env.Preference.AdjustSplit ? this.CalcSplitRatio(today) / base_split : 1; int v = AdjustVolume(GetInt(daily, offset + VOLUME_OFFSET), split); if (is_index || v != 0) { //非indexで出来高0の日は集計しない if (open == 0) open = AdjustPrice(GetInt(daily, offset + OPEN_OFFSET), split); close = AdjustPrice(GetInt(daily, offset + CLOSE_OFFSET), split); high = Math.Max(high, AdjustPrice(GetInt(daily, offset + HIGH_OFFSET), split)); low = Math.Min(low, AdjustPrice(GetInt(daily, offset + LOW_OFFSET), split)); cre_long = AdjustVolume(GetInt(daily, offset + CREDITLONG_OFFSET), split); cre_short = AdjustVolume(GetInt(daily, offset + CREDITSHORT_OFFSET), split); vol += v; } offset += RECORD_LENGTH; if (offset < daily.Length) today = GetInt(daily, offset); } SetInt(_farm, farmoffset, yearmonth.Year * 10000 + yearmonth.Month * 100 + 1); SetInt(_farm, farmoffset + OPEN_OFFSET, open); SetInt(_farm, farmoffset + HIGH_OFFSET, high); SetInt(_farm, farmoffset + LOW_OFFSET, low); SetInt(_farm, farmoffset + CLOSE_OFFSET, close); SetInt(_farm, farmoffset + VOLUME_OFFSET, vol); SetInt(_farm, farmoffset + CREDITLONG_OFFSET, cre_long); SetInt(_farm, farmoffset + CREDITSHORT_OFFSET, cre_short); return offset; } public override int LastDate { get { return _lastDate; } } public override int FirstDate { get { return _firstDate; } } } internal class YearlyDataFarm : DataFarm { private int _firstDate; private int _lastDate; public YearlyDataFarm() : base() { } public override void LoadFor(AbstractBrand br) { _brand = br; Construct(Util.GetDailyDataFileName(br.Code)); } private void Construct(string filename) { _isEmpty = true; if (File.Exists(filename)) { int length = (int)new FileInfo(filename).Length; if (length > 0) { //まずは日足を読む byte[] daily = new byte[length]; _isEmpty = false; FileStream s = null; try { s = new FileStream(filename, FileMode.Open); s.Read(daily, 0, length); } finally { if (s != null) s.Close(); } _firstDate = GetInt(daily, 0); _lastDate = GetInt(daily, daily.Length - RECORD_LENGTH); DateTime yearly_begin = new DateTime(_firstDate / 10000, (_firstDate % 10000) / 100, (_firstDate % 100)); DateTime yearly_end = new DateTime(_lastDate / 10000, (_lastDate % 10000) / 100, (_lastDate % 100)); _filledLength = yearly_end.Year - yearly_begin.Year + 1; _data = new TradeData[_filledLength + Env.CurrentIndicators.GetAddedFutureLength(ChartFormat.Yearly)]; // 以下WeeklyIndexとかぶって冗長 //byte[]部分のデータ読み _farm = new byte[_data.Length * RECORD_LENGTH]; _byteLength = _farm.Length; DateTime yearmonth = yearly_begin; int offset = 0; for (int i = 0; i < _filledLength; i++) { offset = FillYearlyData(i * RECORD_LENGTH, daily, offset, yearmonth); if (offset >= daily.Length) break; yearmonth = yearmonth.AddYears(1); } } } } // このメソッドもWeeklyIndexのFillWeeklyDataとかぶってかなり冗長 private int FillYearlyData(int farmoffset, byte[] daily, int offset, DateTime yearmonth) { DateTime endyear = yearmonth.AddYears(1); int enddate = endyear.Year * 10000 + 101; int high = Int32.MinValue, low = Int32.MaxValue; int open = 0, close = 0, cre_long = 0, cre_short = 0; uint vol = 0; int today = GetInt(daily, offset); bool is_index = _brand.IsBuiltIn; // base_splitを得るのに最初の取引日である 'today' を使うのは誤り。 // 下の、SetInt(_farm, farmoffset, yearmonth.Year * 10000 + yearmonth.Month * 100 + 1); // で、後に式を評価する際に用いられる基準日として月の初日である 'yearmonth.Year * 10000 + yearmonth.Month * 100 + 1' を使っているのだから、 // ここでもこの値を使うべき。 2005/3/15 T. SARUKI // double base_split = this.CalcSplitRatio(Util.DateToInt(yearmonth.Year, 1, 1)); while (offset <= daily.Length - RECORD_LENGTH && today < enddate) { double split = Env.Preference.AdjustSplit ? this.CalcSplitRatio(today) / base_split : 1; int v = AdjustVolume(GetInt(daily, offset + VOLUME_OFFSET), split); if (is_index || v != 0) { //非indexで出来高0の日は集計しない if (open == 0) open = AdjustPrice(GetInt(daily, offset + OPEN_OFFSET), split); close = AdjustPrice(GetInt(daily, offset + CLOSE_OFFSET), split); high = Math.Max(high, AdjustPrice(GetInt(daily, offset + HIGH_OFFSET), split)); low = Math.Min(low, AdjustPrice(GetInt(daily, offset + LOW_OFFSET), split)); cre_long = AdjustVolume(GetInt(daily, offset + CREDITLONG_OFFSET), split); cre_short = AdjustVolume(GetInt(daily, offset + CREDITSHORT_OFFSET), split); vol += (uint)(v / 10); } offset += RECORD_LENGTH; if (offset < daily.Length) today = GetInt(daily, offset); } SetInt(_farm, farmoffset, yearmonth.Year * 10000 + 101); SetInt(_farm, farmoffset + OPEN_OFFSET, open); SetInt(_farm, farmoffset + HIGH_OFFSET, high); SetInt(_farm, farmoffset + LOW_OFFSET, low); SetInt(_farm, farmoffset + CLOSE_OFFSET, close); SetUInt(_farm, farmoffset + VOLUME_OFFSET, vol); SetInt(_farm, farmoffset + CREDITLONG_OFFSET, cre_long); SetInt(_farm, farmoffset + CREDITSHORT_OFFSET, cre_short); return offset; } public override int LastDate { get { return _lastDate; } } public override int FirstDate { get { return _firstDate; } } } //他の銘柄から導出される銘柄 internal class DerivedDataFarm : DataFarm { private int _firstDate; private int _lastDate; private DerivedBrand _derivedBrand; private ChartFormat _chartFormat; public DerivedDataFarm(DerivedBrand br, ChartFormat fmt) : base() { _derivedBrand = br; _chartFormat = fmt; } public override void LoadFor(AbstractBrand br) { Debug.Assert(br is DerivedBrand); _brand = br; _derivedBrand = (DerivedBrand)br; Construct(_derivedBrand); } private void Construct(DerivedBrand br) { DataFarm[] fs = new DataFarm[br.Dependencies.Length]; int len = Int32.MaxValue; int shortest_farm_index = 0; for (int i = 0; i < fs.Length; i++) { DataFarm f = Env.BrandCollection.ReserveFarm(br.Dependencies[i], _chartFormat); if (f.IsEmpty) { _isEmpty = true; return; //一つでも利用不可があればダメ } fs[i] = f; if (f.FilledLength < len) { shortest_farm_index = i; len = f.FilledLength; } } DataFarm shortest_farm = fs[shortest_farm_index]; if (_farm == null || _farm.Length < len * RECORD_LENGTH) _farm = new byte[len * RECORD_LENGTH]; _byteLength = len * RECORD_LENGTH; _data = new TradeData[len + Env.CurrentIndicators.GetAddedFutureLength(_chartFormat)]; _filledLength = len; _isEmpty = false; _firstDate = shortest_farm.GetByIndex(0).Date; _lastDate = shortest_farm.GetByIndex(shortest_farm.FilledLength - 1).Date; //データの構築 本当はここも遅延評価すると効率的だが FillData(len, shortest_farm_index, br, fs); } private void FillData(int len, int shortest_farm_index, DerivedBrand br, DataFarm[] deps) { int[] indexmap = new int[deps.Length]; EvalResult[][] args = new EvalResult[4][]; for (int i = 0; i < 4; i++) { args[i] = new EvalResult[deps.Length]; for (int j = 0; j < deps.Length; j++) args[i][j] = new EvalResult(0); } Indicator[] inds = new Indicator[] { Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Open), Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.High), Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Low), Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Close)}; TradeData[] tds = new TradeData[deps.Length]; Evaluator ev = new Evaluator(br.Name); for (int i = 0; i < len; i++) { ev.BaseIndex = i; //日付の決定 int date = deps[shortest_farm_index].GetByIndex(i).Date; int farmoffset = i * RECORD_LENGTH; for (int j = 0; j < deps.Length; j++) { int candidate = indexmap[j] + 1; //多くの場合日付とindexは一致しているので、DateToIndexの実行回数を減らすためindexmapを用意 TradeData td = candidate < deps[j].TotalLength ? deps[j].GetByIndex(candidate) : null; if (td == null || td.Date != date) { candidate = deps[j].DateToIndex(date); td = deps[j].GetByIndex(candidate); } indexmap[j] = candidate; for (int k = 0; k < inds.Length; k++) args[k][j].DoubleVal = td.GetValue(inds[k]); } //日付 SetInt(_farm, farmoffset, date); //4本値の計算 Expression expr = br.Expression; ev.Args = args[0]; int open = (int)((EvalResult)expr.Apply(ev)).DoubleVal; SetInt(_farm, farmoffset + OPEN_OFFSET, open); ev.Args = args[3]; int close = (int)((EvalResult)expr.Apply(ev)).DoubleVal; SetInt(_farm, farmoffset + CLOSE_OFFSET, close); ev.Args = args[1]; int v1 = (int)((EvalResult)expr.Apply(ev)).DoubleVal; ev.Args = args[2]; int v2 = (int)((EvalResult)expr.Apply(ev)).DoubleVal; //計算式により、それぞれの高値・安値で計算したものが結果としてどうなるかは変わってしまう SetInt(_farm, farmoffset + HIGH_OFFSET, Math.Max(Math.Max(open, close), Math.Max(v1, v2))); SetInt(_farm, farmoffset + LOW_OFFSET, Math.Min(Math.Min(open, close), Math.Min(v1, v2))); } } public override int LastDate { get { return _lastDate; } } public override int FirstDate { get { return _firstDate; } } } //internal delegate double Calculate(Indicator indicator, TradeData data); //節の種類 internal enum Fushi { Unknown, None, High, Low } /// 日足・週足・月足などの1件のレコード internal class TradeData { private DataFarm _farm; private int _index; private int _offset; private double[] _data; private Fushi _fushi; public TradeData(DataFarm farm, int index, int offset) { _farm = farm; _index = index; _offset = offset; _data = new double[Env.CurrentIndicators.IndicatorCount]; _fushi = Fushi.Unknown; for (int i = 0; i < _data.Length; i++) _data[i] = Double.NaN; } public DataFarm Farm { get { return _farm; } } public int Index { get { return _index; } } public int Offset { get { return _offset; } } public TradeData Prev { get { return _index > 0 ? _farm.GetByIndex(_index - 1) : null; } } public TradeData Next { get { return _index < _farm.TotalLength - 1 ? _farm.GetByIndex(_index + 1) : null; } } public bool IsFuture { get { return _index >= _farm.FilledLength; } } public bool CoversDate(int date) { if (date == this.Date) return true; else { int c = this.Date; if (c > date) return false; else { TradeData next = this.Next; return next != null && date < next.Date; } } } public double GetValue(Indicator indicator) { double t = _data[indicator.LaneID]; //overflowによる演算不可はPositiveInfinityであらわす if (Double.IsPositiveInfinity(t)) return Double.NaN; if (!Double.IsNaN(t)) return t; //キャッシュにヒット try { if (indicator.CheckRange(this)) { t = indicator.Calculate(this); _data[indicator.LaneID] = t; } else t = Double.NaN; return t; } catch (TradeDataOverflowException) { //Debug.WriteLine("Out of range!"); _data[indicator.LaneID] = Double.PositiveInfinity; return Double.NaN; } } public int Date { get { return (int)GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Date)); } } public double Open { get { return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Open)); } } public double High { get { return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.High)); } } public double Low { get { return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Low)); } } public double Close { get { return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Close)); } } public double Volume { get { return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Volume)); } } public double CreditLong { get { return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.CreditLong)); } } public double CreditShort { get { return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.CreditShort)); } } //節の計算 public Fushi Fushi { get { if (_fushi != Fushi.Unknown) return _fushi; double h1 = Double.MinValue; double l1 = Double.MaxValue; double h2 = Double.MinValue; double l2 = Double.MaxValue; int fushi = Env.Preference.FushiRange; //あまり端に表示しても仕方ない if (_index < fushi || _index > _farm.FilledLength - fushi) { _fushi = Fushi.None; return _fushi; } for (int i = _index - fushi; i < _index; i++) { h1 = Math.Max(h1, _farm.GetByIndex(i).High); l1 = Math.Min(l1, _farm.GetByIndex(i).Low); } for (int i = _index + 1; i < _index + fushi; i++) { h2 = Math.Max(h2, _farm.GetByIndex(i).High); l2 = Math.Min(l2, _farm.GetByIndex(i).Low); } //過去に同値があるときは無視、未来にあるときは節 if (h1 < this.High && h2 <= this.High) _fushi = Fushi.High; else if (l1 > this.Low && l2 >= this.Low) _fushi = Fushi.Low; else _fushi = Fushi.None; return _fushi; } } } internal class TradeDataOverflowException : ApplicationException { public TradeDataOverflowException(string msg) : base(msg) { } } internal class IndicatorTimeSeries : TimeSeries { protected DataFarm _farm; protected int _begin; protected int _end; protected Indicator _indicator; public IndicatorTimeSeries(DataFarm farm, Indicator ind, int begin, int end) { _farm = farm; _begin = begin; _end = end; _indicator = ind; } public override int Count { get { return _end - _begin; } } public int BeginIndex { get { return _begin; } } public int EndIndex { get { return _end; } } public override double LastValue { get { return _farm.GetByIndex(_end - 1).GetValue(_indicator); } } protected class IndicatorCursor : TimeSeries.Cursor { private int _index; private IndicatorTimeSeries _parent; public IndicatorCursor(IndicatorTimeSeries parent) { _parent = parent; _index = _parent._begin; } public override bool HasNext { get { return _index < _parent._end; } } public override double Next { get { return _parent._farm.GetByIndex(_index++).GetValue(_parent._indicator); } } } public override Cursor CreateCursor() { return new IndicatorCursor(this); } } }