Example #1
0
        public Quotation[] GetQuotes()
        {
            // TODO: Return the list of quotes for the specified ticker.

            List <Quotation> QList = new List <Quotation>();

            foreach (Ticker item in this.Data)
            {
                Quotation qt = new Quotation();

                qt.High   = item.high;
                qt.Low    = item.low;
                qt.Open   = item.open;
                qt.Price  = item.close;
                qt.Volume = item.volumefrom; ///item.volumeto - item.volumefrom;

                // Date
                AmiDate tickerDate = new AmiDate(HistoryData.UnixTimeStampToDateTime(item.time));
                qt.DateTime = tickerDate.ToUInt64();

                QList.Add(qt);
            }

            return(QList.ToArray());
        }
Example #2
0
        // Получение котировок за последние 24 часа
        public static List <Ticker> getLast24hBars(string pairName)
        {
            List <Ticker> result = new List <Ticker>();

            // Строка запроса - данные за последние 24 часа в 1 мин интервале
            string url = String.Format(LAST24_BARS, pairName, "1m");
            // Получаем ответ
            string answer = getJSONData(url);

            //Log.Write(answer, "answer_" + pairName + ".log");
            // Массив данных
            List <ArrayList> decodedList = null;

            try
            {
                decodedList = JsonConvert.DeserializeObject <List <ArrayList> >(answer);
            }
            catch (Exception e)
            {
                Log.Write("Can't parse JSON data for last 24H bars!");
                return(null);
            }

            if (decodedList.Count == 0)
            {
                Log.Write("Decooded JSON data for last 24H bars is empty!");
                return(null);
            }

            int index = 0;

            foreach (ArrayList arr in decodedList)
            {
                Ticker ticker = new Ticker();

                // Формирование даты
                AmiDate time = new AmiDate(Utils.UnixTimeStampToDateTime(Convert.ToUInt64(arr[0]) / 1000));

                // Bar
                ticker.time   = time.ToUInt64();
                ticker.open   = float.Parse(arr[1].ToString().Replace(".", ","));
                ticker.high   = float.Parse(arr[2].ToString().Replace(".", ","));
                ticker.low    = float.Parse(arr[3].ToString().Replace(".", ","));
                ticker.close  = float.Parse(arr[4].ToString().Replace(".", ","));
                ticker.volume = float.Parse(arr[5].ToString().Replace(".", ","));

                // Запись в массив
                result.Add(ticker);
            }

            // Возвращаем массив
            return(result);
        }
Example #3
0
        public List <Quotation> GetQuotesList()
        {
            List <Quotation> result = new List <Quotation>();

            foreach (Ticker item in this.Data)
            {
                Quotation qt = new Quotation();

                qt.High   = item.high;
                qt.Low    = item.low;
                qt.Open   = item.open;
                qt.Price  = item.close;
                qt.Volume = item.volumefrom; ///item.volumeto - item.volumefrom;

                // Date
                AmiDate tickerDate = new AmiDate(HistoryData.UnixTimeStampToDateTime(item.time));
                qt.DateTime = tickerDate.ToUInt64();

                result.Add(qt);
            }

            return(result);
        }
        public static unsafe int GetQuotesEx(string ticker, Periodicity periodicity, int lastValid, int size, Quotation *quotes, GQEContext *context)
        {
            // Статус - в ожидании данных
            Status = StatusCode.Wait;

            #region Данных не было - БД пуста
            if (lastValid < 0)
            {
                List <Ticker> last24hBars = BinanceHelper.getLast24hBars(ticker);

                // Проверка что не пусто
                if (last24hBars == null || last24hBars.Count == 0)
                {
                    return(lastValid + 1);
                }

                lastValid = 0;

                // Если влазим в окно - то i = 0; иначе last24hBars.Count - size

                for (var i = (last24hBars.Count < size) ? 0 : last24hBars.Count - size; i < last24hBars.Count; i++)
                {
                    quotes[lastValid].DateTime = last24hBars[i].time;
                    quotes[lastValid].Open     = last24hBars[i].open;
                    quotes[lastValid].High     = last24hBars[i].high;
                    quotes[lastValid].Low      = last24hBars[i].low;
                    quotes[lastValid].Price    = last24hBars[i].close;
                    quotes[lastValid].Volume   = last24hBars[i].volume;

                    lastValid++;
                }

                // Сохраняем значение символьной пары
                lastTicker = ticker;

                return(lastValid);
            }
            #endregion

            Log.Write("isConn: " + isSocketConnected + " lastT: " + lastTicker + " TC: " + ticker);

            // Проверка на то что не переключили символы + первый запуск
            if (String.IsNullOrEmpty(lastTicker) || !lastTicker.Equals(ticker))
            {
                isFirstRun = true;

                /*
                 * if (wss != null)
                 *  wss.Close();
                 *
                 * // Запуск сокета
                 * wss = new WebSocket(String.Format("wss://stream.binance.com:9443/ws/{0}@kline_{1}",ticker.ToLower(),"1m"));
                 * wss.Opened += new EventHandler(WebSockedOpened);
                 * wss.MessageReceived += Wss_MessageReceived;
                 * // wss.Closed += Wss_Closed;
                 * wss.Error += Wss_Error;
                 * wss.Open();
                 */
                if (!BinanceHelper.CreateWSS(ticker, periodicity))
                {
                    Log.Write("Create WSS failed!");
                    isSocketConnected = false;
                    return(lastValid + 1);
                }
                // BinanceHelper.onWSSMessage = Wss_MessageReceived;
                // isSocketConnected = true;
            }
            else
            {
                isFirstRun = false;
            }

            // Обозначаем тикер
            lastTicker = ticker;

            #region Данные есть и это первый запуск
            if (isFirstRun && lastValid > 0)
            {
                // Получаем данные
                List <Ticker> last24hBars = BinanceHelper.getLast24hBars(ticker);

                // Проверка что не пусто
                if (last24hBars == null || last24hBars.Count == 0)
                {
                    return(lastValid + 1);
                }

                // Кастрируем массив
                for (var i = 0; i < last24hBars.Count; i++)
                {
                    AmiDate lastDate      = new AmiDate(quotes[lastValid].DateTime);
                    AmiDate requestedDate = new AmiDate(last24hBars[i].time);

                    if (requestedDate.CompareTo(lastDate) <= 0)
                    {
                        last24hBars.RemoveAt(0);
                        i--;
                    }
                    else
                    {
                        // Вываливаемся из цикла так как последний элемент явно старше
                        break;
                    }
                }

                // Вариант 1 - Count > size - переносим данные
                if (last24hBars.Count > size)
                {
                    lastValid = 0;

                    // Перенос последних = size данных
                    for (var i = last24hBars.Count - size; i < last24hBars.Count; i++)
                    {
                        quotes[lastValid].DateTime = last24hBars[i].time;
                        quotes[lastValid].Open     = last24hBars[i].open;
                        quotes[lastValid].High     = last24hBars[i].high;
                        quotes[lastValid].Low      = last24hBars[i].low;
                        quotes[lastValid].Price    = last24hBars[i].close;
                        quotes[lastValid].Volume   = last24hBars[i].volume;

                        lastValid++;
                    }

                    return(lastValid);
                }

                // Вариант 2 - Count < size и входит в окно - добавить в список
                if ((last24hBars.Count < size) && (last24hBars.Count < (size - lastValid)))
                {
                    // Перенос всех из массива
                    for (var i = 0; i < last24hBars.Count; i++)
                    {
                        quotes[lastValid].DateTime = last24hBars[i].time;
                        quotes[lastValid].Open     = last24hBars[i].open;
                        quotes[lastValid].High     = last24hBars[i].high;
                        quotes[lastValid].Low      = last24hBars[i].low;
                        quotes[lastValid].Price    = last24hBars[i].close;
                        quotes[lastValid].Volume   = last24hBars[i].volume;

                        lastValid++;
                    }

                    return(lastValid);
                }
                else
                if ((last24hBars.Count < size) && (last24hBars.Count > (size - lastValid)))
                {
                    // Вариант 3 - данные в окно не входят - нужно сдвигать массив

                    lastValid = 0;

                    // Сколько элементов останется
                    var j = size - last24hBars.Count;
                    // Индекс начала копирования
                    var index = lastValid - j;

                    // Смещение первой части
                    while (lastValid < j)
                    {
                        Log.Write("I: " + lastValid + " HIGH: " + quotes[index].High + " VOL: " + quotes[index].Volume, "3.txt");

                        quotes[lastValid].DateTime = quotes[index].DateTime;
                        quotes[lastValid].Open     = quotes[index].Open;
                        quotes[lastValid].High     = quotes[index].High;
                        quotes[lastValid].Low      = quotes[index].Low;
                        quotes[lastValid].Price    = quotes[index].Price;
                        quotes[lastValid].Volume   = quotes[index].Volume;

                        lastValid++;
                        index++;
                    }

                    // КОпируем остатки
                    foreach (Ticker item in last24hBars)
                    {
                        Log.Write("I: " + lastValid + " HIGH: " + item.high + " VOL: " + item.volume, "4.txt");

                        quotes[lastValid].DateTime = item.time;
                        quotes[lastValid].Open     = item.open;
                        quotes[lastValid].High     = item.high;
                        quotes[lastValid].Low      = item.low;
                        quotes[lastValid].Price    = item.close;
                        quotes[lastValid].Volume   = item.volume;

                        lastValid++;
                    }

                    return(lastValid);
                }
            }

            #endregion

            // не первый запуск - просто обновляем

            // Если не подключились - нечего разбирать
            if (!isSocketConnected)
            {
                return(lastValid + 1);
            }

            // Показывает что данные тикера устарели
            //bool isTooOld = false;
            BinanceData data = null;

            if (!String.IsNullOrEmpty(jsonAnswer))
            {
                // Парсинг
                try
                {
                    data = JsonConvert.DeserializeObject <BinanceData>(jsonAnswer);
                }
                catch (Exception e)
                {
                    Log.Write("Parse Error: " + e.Message);
                    return(lastValid + 1);
                }
            }

            // В БД есть какие-то данные
            if (lastValid >= 0)
            {
                ulong lastDate   = quotes[lastValid].DateTime;
                ulong tickerDate = (new AmiDate(Utils.UnixTimeStampToDateTime(data.k.t / 1000))).ToUInt64();

                /*
                 * if (tickerDate < lastDate)
                 *  isTooOld = true;
                 */

                if (tickerDate > lastDate)
                {
                    lastValid++;
                }
            }
            else
            {
                // Если пусто в БД - начинаем писать с 0го  индекса
                lastValid = 0;
            }


            // Поправка на лимит
            if (size > 0 && lastValid == size)
            {
                // Сдвигание массива влево
                for (int i = 0; i < size - 1; i++)
                {
                    quotes[i].DateTime = quotes[i + 1].DateTime;
                    quotes[i].Open     = quotes[i + 1].Open;
                    quotes[i].High     = quotes[i + 1].High;
                    quotes[i].Low      = quotes[i + 1].Low;
                    quotes[i].Price    = quotes[i + 1].Price;
                    quotes[i].Volume   = quotes[i + 1].Volume;
                }

                lastValid--;
            }

            // Правим
            //if (!isTooOld)
            {
                AmiDate tickerDate = new AmiDate(Utils.UnixTimeStampToDateTime(data.k.t / 1000));

                quotes[lastValid].DateTime = tickerDate.ToUInt64();
                quotes[lastValid].Open     = float.Parse(data.k.o.Replace(".", ","));
                quotes[lastValid].High     = float.Parse(data.k.h.Replace(".", ","));
                quotes[lastValid].Low      = float.Parse(data.k.l.Replace(".", ","));
                quotes[lastValid].Price    = float.Parse(data.k.c.Replace(".", ","));
                quotes[lastValid].Volume   = float.Parse(data.k.v.Replace(".", ","));
                quotes[lastValid].AuxData1 = 0;
                quotes[lastValid].AuxData2 = 0;
            }

            Status = StatusCode.OK;
            return(lastValid + 1);
        }
        public static unsafe int GetQuotesEx(string ticker, Periodicity periodicity, int lastValid, int size, Quotation *quotes, GQEContext *context)
        {
            // Статус - в ожидании данных
            Status = StatusCode.Wait;

            // при запуске из базы
            if (refrashTime == -1)
            {
                refrashTime = (int)periodicity;
            }

            // ticker - наименование пары
            // periodicity - период опроса (day, mminute, ...)

            string baseURL;

            // Если не фьючерсы Okex
            if (ticker.IndexOf("Future Contracts") < 0)
            {
                baseURL = "https://min-api.cryptocompare.com/data/";


                // Вроде как ограничение за последние 30 дней = 30 записей по умолчанию (макс 2000)
                if (periodicity == Periodicity.EndOfDay)
                {
                    baseURL += "histoday?";
                }

                // Вроде как ограничение за последние 7 дней = 168 записей по умолчанию (макс 2000)
                if (periodicity == Periodicity.OneHour)
                {
                    baseURL += "histohour?";
                }

                // Вроде как ограничение за последние 24 часа = 1440 записей по умолчанию (макс 2000)
                if (periodicity == Periodicity.OneMinute)
                {
                    baseURL += "histominute?aggregate=1&";
                }

                // 5 минутная агрегация
                if (periodicity == Periodicity.FiveMinutes)
                {
                    baseURL += "histominute?aggregate=5&";
                }

                // 15 минутная агрегация
                if (periodicity == Periodicity.FifteenMinutes)
                {
                    baseURL += "histominute?aggregate=15&";
                }
            }
            else
            {
                // https://www.okex.com/api/v1/future_kline.do?symbol=btc_usd&contract_type=this_week&type=5min

                baseURL = "https://www.okex.com/api/v1/future_kline.do?type=";

                // Вроде как ограничение за последние 30 дней = 30 записей по умолчанию (макс 2000)
                if (periodicity == Periodicity.EndOfDay)
                {
                    baseURL += "1day";
                }

                // Вроде как ограничение за последние 7 дней = 168 записей по умолчанию (макс 2000)
                if (periodicity == Periodicity.OneHour)
                {
                    baseURL += "1hour";
                }

                // Вроде как ограничение за последние 24 часа = 1440 записей по умолчанию (макс 2000)
                if (periodicity == Periodicity.OneMinute)
                {
                    baseURL += "1min";
                }

                // 5 минутная агрегация
                if (periodicity == Periodicity.FiveMinutes)
                {
                    baseURL += "5min";
                }

                // 15 минутная агрегация
                if (periodicity == Periodicity.FifteenMinutes)
                {
                    baseURL += "15min";
                }
            }
            // 2. Выбираем маркет и пару

            // Разбор вида [1]/[2] - [3]
            // [1] - 1й символ
            // [2] - 2й символ
            // [3] - маркет

            try
            {
                string s1     = ticker.Substring(0, ticker.IndexOf("/"));
                string s2     = ticker.Substring(ticker.IndexOf("/") + 1, ticker.IndexOf(" ") - ticker.IndexOf("/"));
                string market = ticker.Substring(ticker.IndexOf(" - ") + 3);

                // Коррекция CryptoCompare
                if (market.IndexOf("CryptoCompare") > -1)
                {
                    market = "CCCAGG";
                }


                if (ticker.IndexOf("Future Contracts") < 0)
                {
                    // Добавим пары и маркет в URL
                    baseURL += "fsym=" + s1.Trim() + "&tsym=" + s2.Trim() + "&e=" + market.Trim() + "&allData=true";
                }
                else
                {
                    //symbol=btc_usd
                    baseURL += "&symbol=" + s1.Trim().ToLower() + "_" + s2.Trim().ToLower() + "&contract_type=" + RightClickMenu.contractType + "&size=" + size;
                }
            }
            catch (Exception e)
            {
                Status = StatusCode.Error;
                Log.Write("The symbol translate error!");

                return(lastValid + 1);
            }

            string jsonResponse = MarketData.getMarketData(baseURL);

            // Если ошибка
            if (String.IsNullOrEmpty(jsonResponse))
            {
                Status = StatusCode.Error;
                Log.Write("Error: " + MarketData.getLastError());
                return(lastValid + 1);
            }

            MarketData.hasData = false;

            // В jsonResponse имеем файл ответа сервера
            // Парсим
            List <Quotation> newQuotes = null;

            if (ticker.IndexOf("Future Contracts") < 0)
            {
                HistoryData data = null;

                try
                {
                    data = JsonConvert.DeserializeObject <HistoryData>(jsonResponse);
                }
                catch (Exception e)
                {
                    Log.Write("Json deserialize error: " + e.Message);
                    return(lastValid + 1);
                }

                // В итоге имеем данные
                newQuotes = data.GetQuotesList();
            }
            else
            {
                IList <IList <string> > data = null;

                try
                {
                    data = JsonConvert.DeserializeObject <IList <IList <string> > >(jsonResponse);
                }
                catch (Exception e)
                {
                    Log.Write("Json deserialize error: " + e.Message);
                    return(lastValid + 1);
                }

                foreach (IList <string> item in data)
                {
                    Quotation qt = new Quotation();

                    AmiDate tickerDate = new AmiDate(HistoryData.UnixTimeStampToDateTime(ulong.Parse(item[0]) / 1000));

                    try
                    {
                        qt.Open   = float.Parse(item[1].Replace(".", ","));
                        qt.High   = float.Parse(item[2].Replace(".", ","));
                        qt.Low    = float.Parse(item[3].Replace(".", ","));
                        qt.Price  = float.Parse(item[4].Replace(".", ","));
                        qt.Volume = float.Parse(item[5].Replace(".", ","));
                    }
                    catch (Exception e)
                    {
                        Log.Write(e.Message);
                    }

                    qt.DateTime = tickerDate.ToUInt64();


                    newQuotes.Add(qt);
                }
            }


            // Запись данных в БД

            #region Пишем с нуля - БД не заполнена
            if (lastValid < 0)
            {
                lastValid = 0;

                // Что меньше: длинна полученных данных или Limit?
                int count = Math.Min(size, newQuotes.Count);
                int i = 0, j = 0;

                for (i = 0; i < count; i++)
                {
                    j = newQuotes.Count - count + i;

                    // Date
                    quotes[i].DateTime = newQuotes[j].DateTime;

                    // ticker
                    quotes[i].Open   = newQuotes[j].Open;
                    quotes[i].High   = newQuotes[j].High;
                    quotes[i].Low    = newQuotes[j].Low;
                    quotes[i].Price  = newQuotes[j].Price;
                    quotes[i].Volume = newQuotes[j].Volume;

                    // Extra data
                    quotes[i].OpenInterest = 0;
                    quotes[i].AuxData1     = 0;
                    quotes[i].AuxData2     = 0;

                    lastValid++;
                }

                // Меняем статус на ОК
                if (!isUpdateAvailable)
                {
                    Status = StatusCode.OK;
                }
                else
                {
                    Status = StatusCode.Update;
                }

                return(lastValid);
            }

            #endregion

            #region Кастрация данных до последнего тайминга

            for (var i = 0; i < newQuotes.Count; i++)
            {
                // Сейчас у нас в массиве только свежак
                if (newQuotes[i].DateTime < quotes[lastValid].DateTime)
                {
                    newQuotes.RemoveAt(i);
                    i--;
                }
            }

            #endregion

            // Если нечего показывать - выход
            if (newQuotes.Count == 0)
            {
                return(lastValid + 1);
            }

            #region Данные уже есть в БД - в корридор входим

            if (newQuotes.Count <= (size - lastValid - 1))
            {
                foreach (Quotation item in newQuotes)
                {
                    // Date
                    quotes[lastValid].DateTime = item.DateTime;

                    // ticker
                    quotes[lastValid].Open   = item.Open;
                    quotes[lastValid].High   = item.High;
                    quotes[lastValid].Low    = item.Low;
                    quotes[lastValid].Price  = item.Price;
                    quotes[lastValid].Volume = item.Volume;

                    // Extra data
                    quotes[lastValid].OpenInterest = 0;
                    quotes[lastValid].AuxData1     = 0;
                    quotes[lastValid].AuxData2     = 0;

                    lastValid++;
                }

                // Меняем статус на ОК
                if (!isUpdateAvailable)
                {
                    Status = StatusCode.OK;
                }
                else
                {
                    Status = StatusCode.Update;
                }

                return(lastValid);
            }

            #endregion

            #region Данные уже есть в БД - в корридор не входим - сдвиг массива

            lastValid = 0;

            // Смещение первой части
            while (lastValid < (size - newQuotes.Count))
            {
                quotes[lastValid].DateTime = quotes[lastValid + newQuotes.Count].DateTime;
                quotes[lastValid].Open     = quotes[lastValid + newQuotes.Count].Open;
                quotes[lastValid].High     = quotes[lastValid + newQuotes.Count].High;
                quotes[lastValid].Low      = quotes[lastValid + newQuotes.Count].Low;
                quotes[lastValid].Price    = quotes[lastValid + newQuotes.Count].Price;
                quotes[lastValid].Volume   = quotes[lastValid + newQuotes.Count].Volume;

                lastValid++;
            }

            // КОпируем остатки
            foreach (Quotation item in newQuotes)
            {
                quotes[lastValid].DateTime = item.DateTime;
                quotes[lastValid].Open     = item.Open;
                quotes[lastValid].High     = item.High;
                quotes[lastValid].Low      = item.Low;
                quotes[lastValid].Price    = item.Price;
                quotes[lastValid].Volume   = item.Volume;

                lastValid++;
            }

            // Меняем статус на ОК
            if (!isUpdateAvailable)
            {
                Status = StatusCode.OK;
            }
            else
            {
                Status = StatusCode.Update;
            }

            return(lastValid);

            #endregion
        }
Example #6
0
        internal override bool Process(FTController ibController, bool allowNewRequest)
        {
            int requestTimeoutPeriod = 75;

            // if contract of the ticker is still being retrieved or headtimestamp of the ticker is needed (not Offline) AND not yet retrieved
            if (TickerData.ContractStatus <= ContractStatus.WaitForResponse ||
                ((FTDataSource.Periodicity == Periodicity.EndOfDay || FTDataSource.AllowMixedEODIntra) && TickerData.HeadTimestampStatus <= HeadTimestampStatus.WaitForResponse))
            {
                return(allowNewRequest);
            }

            if (TickerData.ContractStatus == ContractStatus.Failed || TickerData.ContractStatus == ContractStatus.Offline ||
                TickerData.HeadTimestampStatus == HeadTimestampStatus.Failed || (TickerData.HeadTimestampStatus == HeadTimestampStatus.Offline && (FTDataSource.Periodicity == Periodicity.EndOfDay || FTDataSource.AllowMixedEODIntra)))
            {
                TickerData.QuoteDataStatus = QuotationStatus.Failed;

                IsFinished = true;
                return(allowNewRequest);
            }

            lock (TickerData)   // request handling
            {
                // if reqHistoricalData is send to IB and we are waiting for answer
                if (WaitingForResponse)
                {
                    // request is not yet timed out...
                    if (RequestTime.AddSeconds(requestTimeoutPeriod) > DateTime.Now)
                    {
                        return(allowNewRequest);
                    }

                    // no response arrived in time, request is timed out...
                    LogAndMessage.LogAndQueue(TickerData, MessageType.Info, "Historical data request has timed out. " + ToString(true, LogAndMessage.VerboseLog));

                    RequestTimeouts++;
                    WaitingForResponse = false;

                    // if there were too many reqHistoricalData timeouts
                    if (RequestTimeouts > 2)
                    {
                        // drop this ticker...
                        TickerData.QuoteDataStatus = QuotationStatus.Failed;

                        IsFinished = true;
                        return(allowNewRequest);
                    }
                }

                // if no new request can be sent (request pacing)
                bool histThrottling = !allowNewRequest || TickerData.QuoteDataStatus > QuotationStatus.New && RequestTime.AddSeconds(6.5) > DateTime.Now;

                // process the ticker depending on its state
                switch (TickerData.QuoteDataStatus)
                {
                case QuotationStatus.Offline:

                    LogAndMessage.Log(MessageType.Error, "Program error. Offline ticker cannot get historical update.");

                    IsFinished = true;
                    return(allowNewRequest);

                // All historical data requests are processed for the ticker
                // (the last CalcNextHistoricalDataRequest call sets this state)
                case QuotationStatus.DownloadedEod:

                    #region Merging and backadjusting downloaded quotes of different contracts/expiry into a simgle QuotationList of the continuous contract

                    if (TickerData.SymbolParts.IsContinuous)
                    {
                        QuotationList mergedQuotes = new QuotationList(FTDataSource.Periodicity);

                        int newQuoteIndex;

                        foreach (ContractDetails cd in TickerData.contractDetailsList)
                        {
                            // if there were no quotes receiced for this contract...
                            if (!TickerData.ContinuousQuotesDictionary.ContainsKey(cd.Contract.LocalSymbol))
                            {
                                continue;
                            }

                            newQuoteIndex = 0;

                            if (mergedQuotes.Count > 0)
                            {
                                int     mergedQuoteIndex    = mergedQuotes.Count - 1;
                                AmiDate mergedQuoteDateTime = mergedQuotes[mergedQuoteIndex].DateTime;

                                // move forward to the first quote not overlqapping with prev contract
                                while (newQuoteIndex < TickerData.ContinuousQuotesDictionary[cd.Contract.LocalSymbol].Count - 1 && TickerData.ContinuousQuotesDictionary[cd.Contract.LocalSymbol][newQuoteIndex].DateTime.Date < mergedQuoteDateTime.Date)
                                {
                                    newQuoteIndex++;
                                }

                                // at this point newQuoteIndex points to a quote of the "same" date as mergedQuoteDateTime (if there are quotes for the same day, if not, then the next day)

                                // if daily database then we look for a day where volume on older contract is greater (switch over day)
                                if (FTDataSource.Periodicity == Periodicity.EndOfDay)
                                {
                                    // find the quote that has a lower volume
                                    while (newQuoteIndex > 0 && mergedQuoteIndex > 0 &&
                                           TickerData.ContinuousQuotesDictionary[cd.Contract.LocalSymbol][newQuoteIndex].DateTime.Date == mergedQuotes[mergedQuoteIndex].DateTime.Date &&        // quotes are of same date
                                           TickerData.ContinuousQuotesDictionary[cd.Contract.LocalSymbol][newQuoteIndex].Volume > mergedQuotes[mergedQuoteIndex].Volume)                         // new contract's volume is higher then old contract's volume
                                    {
                                        newQuoteIndex--;
                                        mergedQuoteIndex--;
                                    }
                                    // at this point newQuoteIndex and lastQuoteDateTime point to quote at which contract is replaced
                                }

                                if (TickerData.ContinuousQuotesDictionary[cd.Contract.LocalSymbol][newQuoteIndex].DateTime.Date != mergedQuotes[mergedQuoteIndex].DateTime.Date)
                                {
                                    LogAndMessage.Log(MessageType.Info, TickerData.ToString(cd.Contract) + ": No overlapping quote found. Used dates to change contracts: " + mergedQuotes[mergedQuoteIndex].DateTime + " and " + TickerData.ContinuousQuotesDictionary[cd.Contract.LocalSymbol][newQuoteIndex].DateTime + ".");
                                }
                                else
                                {
                                    LogAndMessage.Log(MessageType.Info, TickerData.ToString(cd.Contract) + ": Switching to new contract on " + mergedQuotes[mergedQuoteIndex].DateTime + ".");
                                }

                                // get "closing prices" of the contract on the same day
                                float  closeOfNewer = TickerData.ContinuousQuotesDictionary[cd.Contract.LocalSymbol][newQuoteIndex].Price;
                                float  closeOfOlder = mergedQuotes[mergedQuoteIndex].Price;
                                double priceMult    = closeOfNewer / closeOfOlder;

                                // back-adjust prev contracts' prices
                                QuotationList tempList = new QuotationList(FTDataSource.Periodicity);
                                for (int i = 0; i < mergedQuoteIndex; i++)
                                {
                                    Quotation quote = mergedQuotes[i];
                                    quote.Open  = (float)(quote.Open * priceMult);
                                    quote.High  = (float)(quote.High * priceMult);
                                    quote.Low   = (float)(quote.Low * priceMult);
                                    quote.Price = (float)(quote.Price * priceMult);
                                    tempList.Merge(quote);
                                }
                                mergedQuotes.Clear();
                                mergedQuotes = tempList;
                            }

                            // add quotes of newer contract
                            for (; newQuoteIndex < TickerData.ContinuousQuotesDictionary[cd.Contract.LocalSymbol].Count; newQuoteIndex++)
                            {
                                mergedQuotes.Merge(TickerData.ContinuousQuotesDictionary[cd.Contract.LocalSymbol][newQuoteIndex]);
                            }
                        }

                        TickerData.Quotes = mergedQuotes;
                    }

                    #endregion

                    // this is not THROTTLED, but counted in general throttling queue
                    ibController.SendSubscriptionRequest(0, TickerData, false);

                    TickerData.QuoteDataStatus = QuotationStatus.Online;

                    return(allowNewRequest);

                // this should never happen (ticker with online status should not be in the queue...)
                case QuotationStatus.Online:

                    LogAndMessage.LogAndQueue(TickerData, MessageType.Info, "Backfill finished, symbol is ready. ");

                    IsFinished = true;
                    return(allowNewRequest);

                // if any error happend
                case QuotationStatus.Failed:

                    // if intraday download received no data response
                    if (errorCode == 162 && FTDataSource.Periodicity < Periodicity.EndOfDay && FTDataSource.Periodicity > Periodicity.FifteenSeconds)
                    {
                        errorCode = 0;

                        // move forward 4 periods to speed up download/find first valid period with available data
                        CalcNextBackfillRequest();
                        CalcNextBackfillRequest();
                        CalcNextBackfillRequest();
                        LogAndMessage.Log(TickerData, MessageType.Trace, "No data returned, fast forward download period.");

                        // start next download
                        TickerData.QuoteDataStatus = QuotationStatus.DownloadingIntra;

                        return(allowNewRequest);
                    }
                    else
                    {
                        LogAndMessage.LogAndQueue(TickerData, MessageType.Info, "Backfill failed, symbol is offline.");

                        IsFinished = true;
                        return(allowNewRequest);
                    }

                // start historical data refresh
                case QuotationStatus.New:

                    if (histThrottling)
                    {
                        return(false);
                    }

                    // calc download properties
                    downloadPeriodicity = FTDataSource.Periodicity;
                    downloadStep        = IBClientHelper.GetDownloadStep(FTDataSource.Periodicity);
                    downloadInterval    = IBClientHelper.GetDownloadInterval(FTDataSource.Periodicity);
                    downloadStart       = IBClientHelper.GetAdjustedStartDate(TickerData.RefreshStartDate, FTDataSource.Periodicity, GetEarliestDownloadDate(), true);
                    downloadEnd         = downloadStart.AddMinutes(downloadInterval);
                    downloadContract    = GetCurrentContract(downloadStart);

                    // remove quotes already stored
                    TickerData.Quotes.Clear();

                    // set next state
                    if (FTDataSource.Periodicity == Periodicity.EndOfDay)
                    {
                        TickerData.QuoteDataStatus = QuotationStatus.DownloadingEod;
                    }
                    else
                    {
                        TickerData.QuoteDataStatus = QuotationStatus.DownloadingIntra;
                    }

                    // not to wait to send next request
                    RequestTime = DateTime.MinValue;

                    // download historical data
                    SendBackfillRequest(ibController);

                    return(false);

                case QuotationStatus.DownloadingEod:
                case QuotationStatus.DownloadingIntra:

                    if (histThrottling)
                    {
                        return(false);
                    }

                    // if previous request timed out
                    if (RequestTimeouts != 0)
                    {
                        SendBackfillRequest(ibController);
                    }

                    // download historical data
                    else if (CalcNextBackfillRequest())
                    {
                        SendBackfillRequest(ibController);
                    }

                    return(false);

                // last CalcNextHistoricalDataRequest call for intraday bars should have set this state
                case QuotationStatus.DownloadedIntra:

                    // if we need EOD data as well
                    if (FTDataSource.AllowMixedEODIntra)
                    {
                        if (histThrottling)
                        {
                            return(false);
                        }

                        // calc download properties for EOD
                        downloadPeriodicity = Periodicity.EndOfDay;
                        downloadStep        = IBClientHelper.GetDownloadStep(Periodicity.EndOfDay);
                        downloadInterval    = IBClientHelper.GetDownloadInterval(Periodicity.EndOfDay);
                        downloadStart       = IBClientHelper.GetAdjustedStartDate(TickerData.RefreshStartDate, Periodicity.EndOfDay, GetEarliestDownloadDate(), true);
                        downloadEnd         = downloadStart.AddMinutes(downloadInterval);
                        downloadContract    = GetCurrentContract(downloadStart);

                        SendBackfillRequest(ibController);

                        TickerData.QuoteDataStatus = QuotationStatus.DownloadingEod;
                    }
                    else
                    {
                        TickerData.QuoteDataStatus = QuotationStatus.DownloadedEod;
                    }

                    return(false);

                default:

                    LogAndMessage.LogAndQueue(TickerData, MessageType.Info, "Program error in backfill logic.");

                    IsFinished = true;
                    return(true);
                }
            }
        }