/// <summary> /// Constructor /// </summary> /// <param name="timeframe">ForexConnect timeframe descriptor</param> internal TimeframeItem(O2GTimeframe timeframe) { mTimeframe = timeframe; DateTime start = DateTime.Now.AddDays(-1), end = DateTime.Now; // parse the timeframe ID to get Quotes Manager timeframe descriptor if (!CandlePeriod.parsePeriod(timeframe.ID, ref mTimeframeUnit, ref mTimeframeLength)) { throw new ArgumentException("Invalide timeframe", "timeframe"); } // get a candle in that timeframe to get it length CandlePeriod.getCandle(DateTime.Now, ref start, ref end, mTimeframeUnit, mTimeframeLength, 0, 0); mLength = end.Subtract(start); }
/// <summary> /// Handling one tick /// </summary> /// <param name="offer"></param> private void HandleOffer(IOffer offer) { lock (mPeriods) { // calculate the start time of the period to which the tick belong to DateTime start = DateTime.MinValue, end = DateTime.MinValue; // calculate candle in EST time because the trading day is always closed by New York time // so to avoid handling different hour depending on daylight saying time - use EST always // for candle calculations // NOTE: for real application this part can be optimized. The candle calculation // is quite complex process, so it is better to avoid it when it is not actually required. // the way to optimize it is to keep end time of the period and check whether the tick belongs to // the period using the following condition start <= tick < end // so the calculation of new candle will be used only when tick is actually >= of the end // of the current candle. CandlePeriod.getCandle(mController.UtcToEst(offer.LastUpdate), ref start, ref end, mTimeframeUnit, mTimeframeLength, mTradingDayOffset, -1); start = mController.EstToUtc(start); // calculate the serial number of minute (for easier comparing) long currMinute = DateToMinute(offer.LastUpdate); if (mPeriods.Count == 0) { // if here is no data in the collection yet - just add a dummy candle mPeriods.Add(new Period(start, offer.Bid, offer.Ask, offer.MinuteVolume)); mLastMinute = currMinute; mLastMinuteVolume = offer.MinuteVolume; } else { // otherwise get the most recent candle Period period = mPeriods[mPeriods.Count - 1]; if (period.Time == start) { // if tick belongs to that period... // update the latest (close) price of bid and ask bars period._Ask.Close = offer.Ask; period._Bid.Close = offer.Bid; // if tick higher than high value of bars - update if (period._Ask.High < offer.Ask) { period._Ask.High = offer.Ask; } if (period._Bid.High < offer.Bid) { period._Bid.High = offer.Bid; } // if tick lower than low value of bars - update if (period._Ask.Low > offer.Ask) { period._Ask.Low = offer.Ask; } if (period._Bid.Low > offer.Bid) { period._Bid.Low = offer.Bid; } // here is a trick. // we don't receive EVERY tick, so we can't simply count them. // It is not a problem for calculating open, high, low and close, because // the tick filter keeps every first, last, and the current extremum ticks // In order to make the volume calculation also correct, the server // broadcasts the accumulated tick volume for the current minute. // so, if the tick belongs to the same minute as the previous tick - // we must substract previous accumulated volume and add a new value. // If the tick is the first tick of a new minute - we must simply // add new accumulated value. if (mLastMinute == currMinute) { period.Volume -= mLastMinuteVolume; period.Volume += offer.MinuteVolume; } else if (currMinute > mLastMinute) { period.Volume += offer.MinuteVolume; } mLastMinute = currMinute; mLastMinuteVolume = offer.MinuteVolume; } else if (period.Time < start) { // this is a first tick of new period, simply create this period // please pay attention that we don't use the first tick as an open // value but use the previous close instead. // This is how the current system works by default. // soon, here should be an option to use the first tick for the open // price instead. mPeriods.Add(period = new Period(start, period.Bid.Close, period.Ask.Close, offer.MinuteVolume)); // update the latest (close) price of bid and ask bars period._Ask.Close = offer.Ask; period._Bid.Close = offer.Bid; // if tick higher than high value of bars - update if (period._Ask.High < offer.Ask) { period._Ask.High = offer.Ask; } if (period._Bid.High < offer.Bid) { period._Bid.High = offer.Bid; } // if tick lower than low value of bars - update if (period._Ask.Low > offer.Ask) { period._Ask.Low = offer.Ask; } if (period._Bid.Low > offer.Bid) { period._Bid.Low = offer.Bid; } mLastMinute = currMinute; mLastMinuteVolume = offer.MinuteVolume; } else { // yep, it is possible that tick is older than the last candle. // it may happen because we start to collect ticks actually BEFORE // we sent the request to the server. So on the border of the minute // it is possible that we "catch" some ticks of the previous // minute // so, simply ignore them ; } } } }